views:

28

answers:

2

I would like to create an abstract class which will create concrete instances depending on initialization parameter. Example:

class SomethingGeneric
def self.new(type, 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?

+1  A: 

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
Derick Bailey
A: 

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!"
Adam Tanner