Ruby 中的工厂方法

Posted

技术标签:

【中文标题】Ruby 中的工厂方法【英文标题】:Factory methods in Ruby 【发布时间】:2010-12-03 16:30:51 【问题描述】:

让单个构造函数返回适当类型的对象的最巧妙、最类似于 Ruby 的方法是什么?

更具体地说,这是一个虚拟示例:假设我有两个类BikeCar,它们是Vehicle 的子类。我想要这个:

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

我在下面提出了一个解决方案,但它使用了allocate,这似乎过于繁琐。还有哪些其他方法,或者我的方法真的可以吗?

【问题讨论】:

你可以使用 mixins 吗?我的意思是你必须有自行车和汽车的课程吗?你能不能有一个 Bike 和 Car mixin 可以包含或扩展到构造函数中创建的对象中。 嗯,我想原则上是这样——尽管这更像是一种 hack——正确的 OO 概念是生成的对象“是”自行车或汽车,而不是“表现得像”自行车或汽车。 您的代码如何知道需要什么样的对象?是否涉及某种查找表? 看起来您有复杂的构造要求。将其放入继承层次结构中会掩盖业务逻辑。要么接受您需要“MountainBike”等的特定子类,要么按照其他人的建议将构造逻辑封装在单独的工厂类中。随着时间的推移,这通常是最好的方法。 【参考方案1】:

如果我创建了一个未调用的工厂方法1newinitialize,我想这并不能真正回答“我如何创建...构造函数。 ..",但我认为我会这样做...

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 在 cmets 中的建议。

【讨论】:

这就是我要采取的方法,这样您就不必担心new 的工作原理。 我建议不要称它为“工厂”。这将模式与实现混淆了。而是将其命名为“create”或“from_style”。【参考方案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>

请注意,something 需要确保在运行 vehicle_from_name 之前加载这些子类(假设这三个类将位于不同的源文件中),否则超类将无法知道存在哪些子类,即在运行构造函数时,您不能依赖自动加载来拉入这些类。

我通过将所有子类放入例如一个vehicles 子目录并将其添加到vehicle.rb 的末尾:

require 'require_all'
require_rel 'vehicles'

使用require_all gem(位于https://rubygems.org/gems/require_all 和https://github.com/jarmo/require_all)

【讨论】:

根据我写这篇文章时的理解,这不应该奏效。我当时并没有意识到,但这只是因为在某些情况下类变量引用与子类共享。 不错的答案。但是,谁能帮我理解为什么是类级别的变量而不是常量?喜欢:VARIABLES = 而不是 @@vehicles = ?? 一个常数会起作用,甚至会更好,因为“某些情况”不那么令人困惑。 :-) 好电话。 @Surya,常量就是常量。 VEHICLES 初始化后不应更改。 @clacke 类变量完全适合这种情况。不断变化的常数完全违反了最小意外原则。【参考方案3】:

改编自here,我有

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

【讨论】:

我不知道你为什么要寻找比这更远的地方。不确定您所说的“执行繁重”到底是什么意思,但这对我来说就像 Ruby 方式。【参考方案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

【讨论】:

嗯,它很干净,也很流畅——我担心它看起来更像是一个 hack,而不是一个“纯”的解决方案。自行车/是车辆/,而不是自行车/像车辆一样/。这向我表明正确的概念是子类而不是 mixin。 这是一个公平的观点,尽管我认为 Java 世界比 Ruby 世界更能感受到这种区别。 kind_of?is_a? 都为模块返回 true,这对我来说意味着 Ruby 的思维方式是模块可以用于“is-a”隐喻。【参考方案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 link) 一书中看到这个示例。

【讨论】:

这与我所追求的不同 - 我希望工厂方法位于派生对象的超类中。 if(wife_allows?)辉煌【参考方案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>

【讨论】:

这不起作用。你会得到一个SystemStackError: stack level too deep。这说明了一个主要问题:你不能既覆盖 new 又调用它而不做一些额外的工作 只有 Bike 和 Car 是 Vehicle 的子类时才会出现系统堆栈错误 彼得,你说得对,我对那个解决方案有点草率。让我修复它以避免 StackError。

以上是关于Ruby 中的工厂方法的主要内容,如果未能解决你的问题,请参考以下文章

快速理解工厂方法模式,及其在源码中的应用

快速理解工厂方法模式,及其在源码中的应用

Spring中的设计模式:工厂方法模式

结合JDK源码看设计模式——简单工厂工厂方法抽象工厂

设计模式01——静态工厂工厂方法抽象工厂

设计模式之工厂方法模式