2010-10-27 3 views
2

Je voudrais créer une classe abstraite qui créera des instances concrètes en fonction du paramètre d'initialisation. Exemple:Comment créer un "cluster de classe" (classe de fabrique avec des instances de sous-classes concrètes) dans Ruby?

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 

Je voudrais avoir FooSomething < SomethingGeneric, BarSomething < SomethingGeneric et plus encore. Puis quand je fais:

obj = SomethingGeneric.new("foo", arg) 

Je voudrais obtenir FooSomething instance.

Mon problème ici est la "nouvelle" méthode. Je l'ai défini SomethingGeneric.new, mais FooSomething et BarSomething sont sous-classes de SomethingGeneric donc ils héritent de la « nouvelle » méthode qui est appelée avec des arguments faux ici:

obj = a_class.new(arg) 

Une des solutions serait d'utiliser un autre nom pour la méthode d'usine "nouveau". Cependant, je voudrais rester avec commodité et garder la méthode abstraite usine de superclasse nommée «nouveau».

Quelle est la manière la plus propre de résoudre ce problème?

+0

Pourquoi? Oh mon Dieu! Pourquoi? – Ariejan

+0

@Ariejan pourquoi pas? –

Répondre

3

votre nouvelle méthode devrait prendre un param: * args

vous voulez saisir le premier élément de tableau comme type var, puis retirez cet élément du tableau afin que vous puissiez passer le reste des args jusqu'au prochain nouvel appel.

Array # shift vous donnera le premier élément, puis retirez-le.

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 
0

La vraie question est de quoi avez-vous besoin de ce comportement? Il semble que vous venez d'une langue comme Java où les usines et autres sont la norme. Avez-vous besoin de ce comportement pour que vous sachiez que l'objet répondra aux méthodes spécifiques que vous allez utiliser? Pourquoi ne pas utiliser une interface?

Quelque chose comme:

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 

Si vous voulez vraiment utiliser une classe abstraite que vous pourriez faire quelque chose comme:

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 

Dans ce cas, le comportement serait quelque chose comme ceci:

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!" 
Questions connexes