From: Brian Candler Date: 2002-11-10T22:39:52+09:00 Subject: Re: return MyClass.new vs self.type.send :new On Sat, Nov 09, 2002 at 12:52:24AM +0900, ahoward wrote: > def Class > def method > self.type.send :new [or perhaps tidier, "self.class.new"] > end > end ... > will result in this behavior > > sub = SubClass.new > > sub.method.type >> SubClass ... > A recent example of why this might be a good design pattern is that > subclasses of Array must re-write all the 'usefull' methods like select, etc. > to return the SubClass'd type since these methods return an Array. This was actually discussed about a week ago, specifically taking the example of class 'S' derived from 'String', where the inherited '+' method in S returns a String not an S. > I can hear the arguments against : > > "what if the subclassed methods do something perverse with the methods" > > "what if the subclassed methods simply will not work if that is done" > > etc. > > I would say > > Sub-classing should always be reserved to _extend_ objects, old behavior > should always remain - but may be added to. Another objection centres around the arity and semantics of constructors. Your code above assumes that all derived classes will have a constructor which allows zero arguments. But this may not be true. For a start, there are objects for which zero arguments are not meaningful: irb(main):001:0> Regexp.new ArgumentError: wrong # of argument from (irb):1:in `initialize' from (irb):1:in `new' from (irb):1 That's not really a problem because in that case your code would say self.type.send :new (arg) However, say that a base class has a constructor which accepts 1 argument. It does not necessarily follow that all derived classes will have a constructor which accepts 1 argument: a derived class may well have other instance variables which need initialising, and it may only make sense to have a constructor which takes (say) 2 arguments. In that case, self.type.send :new (arg) will fail, even though it is fine in the base class. You could _require_ that all derived classes implement a constructor with the same arity as their parent class even if it would not normally make sense to have one, perhaps taking defaults of 'nil' for the extra arguments it needs. In that case it would be a kind of type coercion operator: "give me an object of type SubClass, even though the parameters I am going to give you are only sufficient to create an object of BaseClass". You can't just create an instance of the base class using the base class's constructor and just 'flip' the type to be SubClass; that would leave the subclass's extra instance variables uninitialised, potentially giving you an invalid object instance. Oh, and remember that inheritance can have multiple levels. So given Base new(arg1) Sub1 < Base new(arg1,arg2) Sub2 < Sub1 new(arg1,arg2,arg3) then Sub2 would have to implement constructors of all three variants, just so that it could use inherited methods from Base and Sub1 which construct new objects of type "self.class" This is all pretty nasty, because you can't write a working implementation of Sub2 without knowing about all the different ancestor classes. It would get even nastier if the subclass constructor has the same arity but different semantics (e.g. Base.new takes a string but Subclass.new takes a Regexp; or Subclass.new takes a string but does something different with it) So, perhaps it would be cleaner to explicitly create an object of the base class and then ask the subclass to "convert" it, which might involve filling in its extra instance values with appropriate values or defaults. class Base def method # instead of self.class.new call: self.class.create_from_Base(Base.new) end def create_from_Base(a) a end end Base.create_from_Base is a virtual method, to be overridden in subclasses. Effectively you have a new type of constructor: "construct given an instance of a parent class", rather than "construct given some parameters". You need a mechanism to perform the chaining required with multiple levels of inheritance: for example, if 'a' is an object of class Sub2, then calling 'a.method' will call Sub2.create_from_Base, but this in turn needs to call Sub1.create_from_Base followed by Sub2.create_from_Sub1. I guess this could be done, by walking self.class.superclass up towards Object. Sounds like quite a lot of scaffolding to run every time you create an object instance though. Regards, Brian.