Factory методы в Ruby
Какой самый гладкий, самый похожий на Ruby способ иметь один конструктор, возвращает объект соответствующего типа?
Более конкретно, здесь фиктивный пример: скажем, у меня есть два класса Bike
и Car
, который является подклассом Vehicle
. Я хочу это:
Vehicle.new('mountain bike') # returns Bike.new('mountain bike')
Vehicle.new('ferrari') # returns Car.new('ferrari')
Я предложил решение ниже, но он использует allocate
, который кажется слишком сложным для реализации. Каковы некоторые другие подходы, или это действительно нормально?
Ответы
Ответ 1
Если я создаю метод factory, который не называется 1new
или initialize
, я думаю, что на самом деле он не отвечает на вопрос "как мне сделать... конструктор...", но я думаю, что как бы я это сделал...
class Vehicle
def Vehicle.factory vt
{ :Bike => Bike, :Car => Car }[vt].new
end
end
class Bike < Vehicle
end
class Car < Vehicle
end
c = Vehicle.factory :Car
c.class.factory :Bike
1. Вызов метода factory отлично работает в этом учебном примере, но IRL вы можете рассмотреть @AlexChaffee советы в комментариях.
Ответ 2
Я сделал это сегодня. В переводе на транспортные средства это выглядело бы так:
class Vehicle
VEHICLES = {}
def self.register_vehicle name
VEHICLES[name] = self
end
def self.vehicle_from_name name
VEHICLES[name].new
end
end
class Bike < Vehicle
register_vehicle 'mountain bike'
end
class Car < Vehicle
register_vehicle 'ferrari'
end
Мне нравится, что метки для классов хранятся с самими классами, а не с информацией о подклассе, хранящемся в суперклассе. Конструктор не называется new
, но я не вижу никакой пользы в использовании этого конкретного имени, и это сделало бы вещи сложнее.
> Vehicle.vehicle_from_name 'ferrari'
=> #<Car:0x7f5780840448>
> Vehicle.vehicle_from_name 'mountain bike'
=> #<Bike:0x7f5780839198>
Обратите внимание, что что-то необходимо для того, чтобы убедиться, что эти подклассы загружены до запуска aut_from_name (предположительно эти три класса будут находиться в разных исходных файлах), в противном случае суперкласс не будет знать, какие подклассы существуют, то есть вы не можете зависеть от автозагрузки потянуть эти классы при запуске конструктора.
Я решил это, поставив все подклассы, например. a vehicles
и добавив это к концу vehicle.rb
:
require 'require_all'
require_rel 'vehicles'
Использует камень require_all
(находится в https://rubygems.org/gems/require_all и https://github.com/jarmo/require_all)
Ответ 3
Адаптировано из здесь, у меня
class Vehicle
def self.new(model_name)
if model_name == 'mountain bike' # etc.
object = Bike.allocate
else
object = Car.allocate
end
object.send :initialize, model_name
object
end
end
class Bike < Vehicle
def initialize(model_name)
end
end
class Car < Vehicle
def initialize(model_name)
end
end
Ответ 4
Как насчет включенного модуля вместо суперкласса? Таким образом, вы все равно получите #kind_of?
для работы, и там по умолчанию не будет new
.
module Vehicle
def self.new(name)
when 'mountain bike'
Bike.new(name)
when 'Ferrari'
Car.new(name)
...
end
end
end
class Bike
include Vehicle
end
class Car
include Vehicle
end
Ответ 5
class VehicleFactory
def new()
if (wife_allows?)
return Motorcycle.new
else
return Bicycle.new
end
end
end
class vehicleUser
def doSomething(factory)
a_vehicle = factory.new()
end
end
и теперь мы можем сделать...
client.doSomething(Factory.new)
client.doSomething(Bicycle)
client.doSomething(Motorcycle)
Этот пример можно увидеть в книге "Шаблоны дизайна в Ruby" (ссылка Amazon).
Ответ 6
Вы можете немного почистить вещи, изменив Vehicle#new
на:
class Vehicle
def self.new(model_name = nil)
klass = case model_name
when 'mountain bike' then Bike
# and so on
else Car
end
klass == self ? super() : klass.new(model_name)
end
end
class Bike < Vehicle
def self.new(model_name)
puts "New Bike: #{model_name}"
super
end
end
class Car < Vehicle
def self.new(model_name)
puts "New Car: #{model_name || 'unknown'}"
super
end
end
Последняя строка Vehicle.new
с тройным выражением важна. Без проверки на klass == self
мы застреваем в бесконечном цикле и генерируем StackError, которые другие указывали ранее. Обратите внимание, что мы должны называть super
круглыми скобками. В противном случае мы бы назвали его аргументами, которые super
не ожидает.
И вот результаты:
> Vehicle.new
New Car: unknown # from puts
# => #<Car:0x0000010106a480>
> Vehicle.new('mountain bike')
New Bike: mountain bike # from puts
# => #<Bike:0x00000101064300>
> Vehicle.new('ferrari')
New Car: ferrari # from puts
# => #<Car:0x00000101060688>