Как создать "кластер классов" (фабричный класс с экземплярами конкретных подклассов) в 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
Тогда я хотел бы иметь FooSomething Я хотел бы получить экземпляр FooSomething. Моя проблема здесь заключается в "новом" методе. Я определил SomethingGeneric.new, однако FooSomething и BarSomething являются подклассами SomethingGeneric, поэтому они наследуют "новый" метод, который вызывается здесь с неверными аргументами: Одним из решений будет использование другого имени для фабричного метода "new". Однако я хотел бы придерживаться удобства и оставить метод фабрики абстрактных суперклассов под названием 'new'. Какой самый чистый правильный способ решить эту проблему?obj = SomethingGeneric.new("foo", arg)
obj = a_class.new(arg)
2 ответа
Ваш новый метод должен принимать один параметр: *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
На самом деле вопрос в том, зачем вам это поведение? Похоже, вы пришли с языка, такого как 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!"