How to return new instance of subclass while initi

2020-08-17 08:14发布

问题:

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?

回答1:

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.



回答2:

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


回答3:

Instead of overriding new, you could create a self.instantiate method which would then call .new.