Как создать «кластер классов» (фабричный класс с экземплярами конкретных подклассов) в Ruby? - PullRequest
2 голосов
/ 27 октября 2010

Я хотел бы создать абстрактный класс, который будет создавать конкретные экземпляры в зависимости от параметра инициализации.Пример:

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

Тогда я бы хотел иметь FooSomething

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

, я получу экземпляр FooSomething.

Моя проблема здесь заключается в «новом» методе.Я определил SomethingGeneric.new, однако FooSomething и BarSomething являются подклассами SomethingGeneric, поэтому они наследуют «новый» метод, который вызывается здесь с неверными аргументами:

obj = a_class.new(arg)

Одним из решений будет использование другого именидля заводского метода «новый».Однако я хотел бы придерживаться удобства и оставить метод фабрики абстрактных суперклассов под названием 'new'.

Какой самый чистый и правильный способ решения этой проблемы?

Ответы [ 2 ]

3 голосов
/ 27 октября 2010

ваш новый метод должен принимать один параметр: * args

вы захотите извлечь первый элемент из массива как ваш тип var, а затем удалить этот элемент из массива, чтобы вы могли передать остальные аргументы до следующего нового вызова.

Array # shift даст вам первый элемент, а затем удалит его.

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 голосов
/ 28 октября 2010

Реальный вопрос в том, зачем вам это поведение? Похоже, вы пришли с языка, такого как Java, где фабрики и тому подобное - норма. Вам нужно такое поведение, чтобы вы знали, что объект будет реагировать на конкретные методы, которые вы собираетесь использовать? Как насчет использования интерфейса?

Что-то вроде:

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

Если вы действительно хотите использовать класс Abstract, вы можете сделать что-то вроде:

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

В этом случае поведение будет примерно таким:

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!"
...