Given a class hierarchy as follows:
class A
def initialize(param)
if param == 1 then
#initialize and return instance of B
else
#initialize and return instance of C
end
end
end
class B < A
end
class C < A
end
Is it possible to actually initialize and return an instance of B
or C
when initializing A
? I.e. my_obj = A.new(param)
would result in my_obj
being an instance of class B
or C
depending on the value of param
, which gets checked in A.initialize(param)
.
In my usecase its only known at runtime which subclass (B
or C
) to use and the parent class (A
) is basically never really used.
I thought it might be a good idea to move the logic of deciding whether B
or C
into their common ancestor.
If this is not possible (or bad style), where should I put the check of param
and the decision which class to initialize?
You're breaking a fundamental OO principle here -- classes should know nothing about their subclasses. Of course, sometimes principles should be broken, but there's no apparent reason to do it here.
A far better solution is to shift the instantiation logic to a factory method in a separate class. The factory method takes the same arguments as the A's initializer above, and returns an instance of the appropriate class.
The thing is, the return value of initialize
is ignored. here's what happens when you call A.new
:
new
calls a special class method called allocate
-- this returns an empty instance of the class
new
then calls initialize
on the object returned by allocate
, and returns the object
To do what you want to do, you need to override new
and make it do what you want:
class A
def self.new(args*)
if(args[0]==1)
B.new(*args[1..-1])
else
C.new(*args[1..-1])
end
end
end
There is something else to consider though. If A
is never really used on its own, you should be using some sort of factory method, or, just a simple if statement. Eg:
def B_or_C(param,args)
(param == 1 ? B : C).new(args)
end
The design really depends on what you are using them for. When you, for example, have a class that could be used to handle multiple versions of something, for example, HTML, you could have a main class HTMLParser
which overrode new
and could return any of its subclasses: HTML1Parser
, HTML2Parser
, HTML3Parser
, HTML4Parser
, and HTML5Parser
.
Note: You have to override the new
method to the default in the sub-classes to prevent infinite looping:
def self.new(args*)
obj=allocate
obj.send(:initialize, *args)
obj
end
Instead of overriding new, you could create a self.instantiate
method which would then call .new
.