How to create a "class cluster" (factory class with instances of concrete subclasses) in Ruby?
I would like to create an abstract class which will create concrete instances depending on initialization parameter. Example:
class SomethingGeneric
def self.new(typ开发者_高级运维e, arg)
class_name = "#{type.capitalize}Something"
if obj.const_defined?(class_name)
a_class = obj.const_get(class_name)
else
raise ArgumentError, "Concrete something '#{type}' was not found"
end
obj = a_class.new(arg)
return obj
end
end # class
Then I would like to have FooSomething < SomethingGeneric, BarSomething < SomethingGeneric and more. Then when I do:
obj = SomethingGeneric.new("foo", arg)
I would get FooSomething instance.
My problem here is the "new" method. I have defined SomethingGeneric.new, however FooSomething and BarSomething are subclasses of SomethingGeneric therefore they inherit the "new" method which is called with wrong arguments here:
obj = a_class.new(arg)
One of the solution would be to use another name for the factory method 'new'. However I would like to stick with convenience and keep the abstract superclass factory method named 'new'.
What is the cleanest correct way to solve this problem?
your new method should take one param: *args
you'll want to grab the first item out of the array as your type var, and then remove that item from the array so you can pass the rest of the args down to the next new call.
Array#shift will give you the first item and then remove it.
class SomethingGeneric
def self.new(*args)
type = args.shift
class_name = "#{type.capitalize}Something"
if obj.const_defined?(class_name)
a_class = obj.const_get(class_name)
else
raise ArgumentError, "Concrete something '#{type}' was not found"
end
obj = a_class.new(*args)
return obj
end
end # class
The real question is what do you need this behavior for? It appears you're coming from a language like Java where Factories and the like are the norm. Do you need this behavior so that you know that the object will respond to specific methods you are going to use? How about using an interface?
Something like:
class GenericThing
def name # Interface method
# Return a String of the name of the GenericThing.
end
end
class GenericSomething
def name
# ...
end
end
class Whatever
def self.method_that_uses_the_generic_interface(generic)
if generic.respond_to?(:name) # Check for interface compliance
generic.name
else
raise "Generic must implement the name method."
end
end
end
If you really want to use an Abstract class you could do something like:
class AbstractGeneric
def name
raise "You must override name in subclasses!"
end
class GenericThing < AbstractGeneric
def name
"GenericThing"
end
end
class GenericSomething < AbstractGeneric
# ...
end
class Whatever
def self.method_that_uses_the_generic_interface(generic)
if generic.kind_of? AbstractGeneric
generic.name
# Will raise if the interface method hasn't been overridden in subclass.
else
raise "Must be a instance of a subclass of AbstractGeneric!"
end
end
end
In this case the behavior would be something like this:
generic_thing = GenericThing.new
Whatever.method_that_uses_the_generic_interface(generic_thing)
=> "GenericThing"
generic_something = GenericSomething.new
Whatever.method_that_uses_the_generic_interface(generic_something)
# Will raise "You must override name in subclass!"
object = Object.new
Whatever.method_that_uses_the_generic_interface(object)
# Will raise "Must be an instance of a subclass of AbstractGeneric!"
精彩评论