Ruby 中的工厂方法
Posted
技术标签:
【中文标题】Ruby 中的工厂方法【英文标题】:Factory methods in Ruby 【发布时间】:2010-12-03 16:30:51 【问题描述】:让单个构造函数返回适当类型的对象的最巧妙、最类似于 Ruby 的方法是什么?
更具体地说,这是一个虚拟示例:假设我有两个类Bike
和Car
,它们是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】:如果我创建了一个未调用的工厂方法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 在 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 中的工厂方法的主要内容,如果未能解决你的问题,请参考以下文章