Фабричные методы в Ruby - PullRequest
       3

Фабричные методы в Ruby

18 голосов
/ 04 октября 2009

Каков самый изящный, наиболее похожий на Ruby способ заставить один конструктор вернуть объект соответствующего типа?

Чтобы быть более конкретным, вот фиктивный пример: скажем, у меня есть два класса Bike и Car, подкласс Vehicle. Я хочу это:

Vehicle.new('mountain bike')  # returns Bike.new('mountain bike')
Vehicle.new('ferrari')        # returns Car.new('ferrari')

Я предложил решение ниже, но оно использует allocate, которое кажется слишком сложным для реализации. Какие есть другие подходы, или у меня все в порядке?

Ответы [ 6 ]

20 голосов
/ 04 октября 2009

Если я создаю фабричный метод, который не называется 1 new или 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 .

17 голосов
/ 09 июня 2011

Я сделал это сегодня. В переводе на транспортные средства это будет выглядеть так:

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>

Обратите внимание, что что-то должно обеспечить загрузку этих подклассов до запуска vehicle_from_name (предположительно, эти три класса будут в разных исходных файлах), иначе суперкласс не сможет узнать, какие существуют подклассы, т. е. вы не можете зависеть от автозагрузки, в которую можно загружать эти классы при запуске конструктора.

Я решил это, поместив все подклассы, например, в. подкаталог vehicles и добавление его в конец vehicle.rb:

require 'require_all'
require_rel 'vehicles'

Использует камень require_all (находится в https://rubygems.org/gems/require_all и https://github.com/jarmo/require_all)

6 голосов
/ 04 октября 2009

Адаптировано с здесь , у меня

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

А как насчет включенного модуля вместо суперкласса? Таким образом, вы все равно получаете #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
2 голосов
/ 04 октября 2009
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 ).

1 голос
/ 04 октября 2009

Вы можете немного почистить вещи, изменив 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>
...