Подтвердить что ты не робот

Factory методы в Ruby

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

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

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

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

4b9b3361

Ответ 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>