Can Ruby do the Objective-C/Cocoa style alloc/init pattern?

In Objective-C/Cocoa objects normally come to life in the following
way:

instance = [[Class alloc] init];

The "Class" is sent an "alloc" message (memory is allocated and a new
instance is actually created) and then the new instance is sent an
"init" message (allowing the object to prepare itself for use).

This pattern allows you do some cool dynamic things. For example, in
your "init" method you can actually return a different object than the
one that was allocated. This is pretty cool because it means you can
return objects of different classes depending on the parameters that
are passed to your "init" method.

Is there any way to do something similar in Ruby? Sending "new" to a
class in Ruby is kind of like doing an "alloc" in Objective-C/Cocoa,
and the Ruby "initialize" method is similar to the Objective-C/Cocoa
"init" method; but unlike Objective-C/Cocoa it really doesn't matter
what you return from your "initialize" method so there doesn't seem to
be any way to return an object of a different class.

I can think of ways to fake this pattern but I am not sure if they're
the best idea. For example, I could add a new class method (could even
call it "alloc" if I wanted) and a new instance method "init" and
basically fake the Cocoa/Objective-C behaviour; but I don't want to
deform Ruby until it looks like Objective-C, I'd rather find a means of
implementing this pattern the "Ruby Way".

What do the experienced Rubyists have to say?

Cheers,
Greg

Alle 10:50, mercoledì 24 gennaio 2007, Greg Hurrell ha scritto:

In Objective-C/Cocoa objects normally come to life in the following
way:

instance = [[Class alloc] init];

The "Class" is sent an "alloc" message (memory is allocated and a new
instance is actually created) and then the new instance is sent an
"init" message (allowing the object to prepare itself for use).

This pattern allows you do some cool dynamic things. For example, in
your "init" method you can actually return a different object than the
one that was allocated. This is pretty cool because it means you can
return objects of different classes depending on the parameters that
are passed to your "init" method.

Is there any way to do something similar in Ruby? Sending "new" to a
class in Ruby is kind of like doing an "alloc" in Objective-C/Cocoa,
and the Ruby "initialize" method is similar to the Objective-C/Cocoa
"init" method; but unlike Objective-C/Cocoa it really doesn't matter
what you return from your "initialize" method so there doesn't seem to
be any way to return an object of a different class.

I can think of ways to fake this pattern but I am not sure if they're
the best idea. For example, I could add a new class method (could even
call it "alloc" if I wanted) and a new instance method "init" and
basically fake the Cocoa/Objective-C behaviour; but I don't want to
deform Ruby until it looks like Objective-C, I'd rather find a means of
implementing this pattern the "Ruby Way".

What do the experienced Rubyists have to say?

Cheers,
Greg

I'm not an experienced rubyst, but I think you need to overload new, not
initialize:

class A
  def initialize
    puts "initialize for class A"
  end
end

class B
  def initialize arg
    puts "initialize for class B. arg is #{arg}"
  end
end

class C
  def initialize
    puts "initialize for class C"
  end
end

class D
  def self.new cls, *args
    const_get(cls).new *args
  end
end

a=D.new 'A'
=> initialize for class A
b=D.new 'B', 1
=> initialize for class A
c=D.new 'C'
=> initialize for class A

puts a.class
=> A
puts b.class
=> B
puts c.class
=> C

At any rate, I'm not sure this is the best way, at least if class D should
never be instantiated. In this case, I'd go with a module:

module Mod

  class A
  end

  class B
  end

  class C
  end

  def Mod.create cls, *args
    const_get(cls).new *args
  end

end

puts Mod.create('A').class
=>A

Stefano

new is written like:

class Object
   def self.new(*args, &block)
     obj = allocate
     obj.send(:initialize, *args, &block) # initialize is private
     obj
   end
end

···

On Jan 24, 2007, at 01:50, Greg Hurrell wrote:

In Objective-C/Cocoa objects normally come to life in the following
way:

instance = [[Class alloc] init];

The "Class" is sent an "alloc" message (memory is allocated and a new
instance is actually created) and then the new instance is sent an
"init" message (allowing the object to prepare itself for use).

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net

I LIT YOUR GEM ON FIRE!

A less drastic approach than overriding new in the class is to simply
write your own specialized constructor, which can itself call new,
allocate, or #initialize, in whatever combination is needed.

class A
   def self.my_constructor(*a, &b)
     # whatever but good idea to ensure that
     # an instance of A is returned.
   end
end
a = A.my_constructor

I think that overriding A#new in such a way that it returns something
other than an instance of A should be avoided. The general principle
is that a class should be a factory for its own instances. If you want
a more general factory pattern, I'd use a module method that delegated
construction to an appropriate class:

class A;end
class B;end
module Factory
   def self.build(a)
     case a
     when 'A' then A.new
     when 'B' then B.new
     else raise ArgumentError, "'A' or 'B' expected"
     end
   end
end

Factory.build 'A' # instance of A
Factory.build 'B' # instance of B
Factory.build 'C' # ArgumentError

Gary Wright

···

On Jan 24, 2007, at 2:07 PM, Eric Hodel wrote:

On Jan 24, 2007, at 01:50, Greg Hurrell wrote:

The "Class" is sent an "alloc" message (memory is allocated and a new
instance is actually created) and then the new instance is sent an
"init" message (allowing the object to prepare itself for use).

new is written like:

class Object
  def self.new(*args, &block)
    obj = allocate
    obj.send(:initialize, *args, &block) # initialize is private
    obj
  end
end

Thankyou to everybody who participated in this thread. I think you've
covered all the bases quite nicely.

Thanks,
Greg