在Sandi Metz的OOP Ruby书的6.5.2节中,讨论了基类发送钩子消息以允许子类在减少耦合的同时贡献信息。
在基类中,local_spares
方法是默认的空散列。
首先,为什么对合并中的local_spares的调用被委托给子类中的local_spares实现,而不是基类中的local_spares ?
class Bicycle
#...
def spares
{tire_size: tire_size,
chain: chain}.merge(local_spares)
end
def local_spares
{}
end
end
class RoadBike < Bicycle
#...
def local_spares
{ tape_color: tape_color }
end
end
是否因为一旦RoadBike继承了Bicycle, spares就成为了RoadBike的一个方法,并且它首先在自己的类中"看起来"?
其次,基类local_spares在这里返回空散列的目的是什么?这个方法可以引发NotImplementedError,而不是强制其实现在子类(模板方法模式)?
首先,为什么对合并中的local_spares的调用被委托给子类中的local_spares的实现,而不是基类中的local_spares ?
这就是Ruby方法查找的工作原理。当您向对象发送消息时,Ruby遍历它的类、超类(es)和(前置/包含的)模块,直到找到一个具有相同名称的对应方法并调用它。
查找从对象的单例类开始,并遵循ancestors
列表:
road_bike = RoadBike.new
road_bike.singleton_class.ancestors
#=> [#<Class:#<RoadBike:0x00007fe0b59b9478>>, RoadBike, Bicycle, Object, Kernel, BasicObject]
当您调用road_bike.spare
时,Ruby在上面的列表中查找spare
实例方法。我们可以在Ruby中像这样重建查找:(非常粗略地)
road_bike.singleton_class.ancestors.find do |mod|
mod.instance_methods(false).include?(:spares)
end
#=> Bicycle
它在Bicycle
中找到spares
并调用它。在该方法中,消息local_spares
被发送到当前接收者(仍然是road_bike
),并再次开始查找:
road_bike.singleton_class.ancestors.find do |mod|
mod.instance_methods(false).include?(:local_spares)
end
#=> RoadBike
这一次,Ruby在RoadBike
中找到了相应的方法。
请注意,具有相同名称的方法可以在祖先列表中存在多次,例如有RoadBike#local_spares
和Bicycle#local_spares
。Ruby总是根据祖先列表调用第一个。但是,您可以通过super
从子类中调用超类方法。
其次,基类local_spares在这里返回空散列的目的是什么?这个方法可以引发NotImplementedError,而不是强制其实现在子类(模板方法模式)?
引发NotImplementedError
在书中的例子中用于default_tire_size
:
def default_tire_size
raise NotImplementedError
end
通过引发这样的错误,可以强制所有子类提供一个实现。
我认为主要的区别是不是所有的自行车变体都有本地备件。它是一个加法被合并到一个现有的散列中。通过在基类中提供默认值,您可以允许子类省略方法实现。
是的,一旦继承了Roadbike, spares就变成了Roadbike的一个方法。至于重写方法"local_spares"在roadbike中,当从roadbike类引用时,它被调用。
是因为一旦RoadBike继承了Bicycle, spares '成为' RoadBike的一个方法,并且它首先在自己的类中'看起来' ?
为什么我们有空散列"local_spares"是因为在Bicycle类中引用了local_spares,所以要避免"method not found";当spares被Bicycle的实例调用时。
根据逻辑,假设spares方法在所有嵌套类中都是通用的,因此它被添加到Bicycle类中。