TL;博士
首先,您最初发布的代码没有定义对象之间的正确关系或协作,这是问题的一部分。其次,需要捕获和转发块的方式通常是相当不直观的,因此使用块而不是简单的依赖注入(例如,使用特定的 Animal 初始化一个新的 Person 以与之协作)或消息传递背后的推理使得这比它需要的要难一些。简单通常更好,并且通常更容易在以后调试和扩展。
也就是说,下面的重新设计在语义上更加清晰,而且还展示了如何在协作者之间将块作为 Proc 对象转发。Person 现在采用一个可选块,并且当存在时将该块作为 Proc 对象传递给 Animal 上可以调用它的方法。
您的代码重新设计
考虑以下重新设计,它努力将正确的行为附加到正确的对象:
class Person
def speaks_to_animal species, phrase, &reaction
animal = Animal.new species
animal.reacts_to phrase, &reaction
end
end
class Animal
attr_reader :species
def initialize species
@species = species.to_s
end
def reacts_to phrase
case species
when 'lion'; pp "The #{species} eats you."
when 'dog'
block_given? ? yield : pp("The #{species} barks at you.")
else pp "The #{species} doesn't know what to do."
end
end
end
特别是,这里的目标是重新设计代码,使 Person#speaks_to_animal 而 Animal#reacts_to_phrase 由 Person 说话。这保持了所属的行为,只要 Person 不 #fetch 并且 Animal 不必知道有关 Person 对象内部的任何信息即可进行协作。
作为副产品,这种重新设计提供了更大的灵活性。您的块现在是可选的,当它们被传递给 Person 时,它们会被 Animal 转发和调用,这似乎是您原始代码的意图。您现在通过 Person 与 Animal 交互,并且该 Person 可以与您选择指定的任何类型的 Animal 物种对话,而无需继承 Animal 或硬编码反应。例如:
person = Person.new
person.speaks_to_animal :dog, "Fetch, boy!"
person.speaks_to_animal(:dog, "Fetch, boy!") do
pp "The dog brings the stick back to you."
end
person.speaks_to_animal :lion, "Fetch, boy!"
如果你不向人传递一个街区,那么狗不知道该怎么做,只会对你吠叫。如果您将行为期望作为一个块传递,该块将被转发到 animal#reacts_to ,并在其中通过 调用它yield
。当然,如果你让狮子玩取物,坏事就会发生。
重新包装对象之间的行为和关系可以让你做各种各样的事情,比如关闭人所说的短语的元素以实现更复杂的反应,或者允许动物关闭短语的元素以根据其物种做出不同的反应. 不过,大多数情况下,这个新代码解决了如何传递一个表示动物反应的可选块而不将对象耦合得太紧的问题。
从语义上讲,一个人应该知道他们在和什么动物说话,以及他们希望动物会做什么回应。传递一个块是否真的是代表 Person 的期望或 Animal 的反应的最佳方式是更有争议的,我个人会选择更多地关注基于物种和短语的定义反应,而不是传递 Proc 对象。你的旅费可能会改变。