我已经浏览了,但没有看到以下问题的答案:
你会使用什么别名方法?
class Vampire
attr_reader :name, :thirsty
alias_method :thirsty?, :thirsty
end
我使用一个的唯一原因是能够在我定义的任何方法中使用问号吗?我相信您不能对实例变量使用问号。
一个会使用Module#alias_method
有两个原因,一个是最新的和有效的,另一个是过时的,无论如何都不是真正必要的。
第一个原因是,您只是希望有两个具有不同名称的方法执行完全相同的操作。其中一个原因可能是,对于同一操作,有两个同样广泛使用的术语,并且您希望让人们更容易编写其社区可以理解的代码。(Ruby 核心库中的一些例子是集合方法,它们的名称是来自函数式编程语言的人所熟悉的,例如map
、reduce
、来自 Smalltalk 编程语言家族的人,例如collect
、inject
、select
和标准英语名称,例如find_all
。另一种可能性是你构建了一个Fluent 接口,并希望它更流畅地阅读,如下所示:
play this
and that
and something_else
在这种情况下,and
可以是play
的别名。
另一个相关原因(我们称之为原因 1.5)是你想要实现某个协议,并且你已经有一个正确实现该协议语义的方法,但它的名称错误。让我们假设一个假设的集合框架,它有两个方法map_seq
和map_nonseq
。第一个执行map
并保证副作用的顺序,而第二个不保证副作用的顺序,甚至可以异步、并发或并行执行映射操作。这两种方法实际上具有不同的语义,但是如果您的数据结构不适合并行化,那么您可以简单地实现map_seq
并map_nonseq
别名。
在这种情况下,驱动程序并不是要为同一操作提供两个名称,而是要为两个名称提供相同的实现(如果该句子:-D有意义)。
过去使用alias_method
的第二个主要原因与其语义的一个重要细节有关:当您覆盖或修补这两种方法中的任何一种时,这只会影响该名称,而不会影响另一个名称。这在过去用于包装方法的行为,如下所示:
class SomeClass
def some_method
"Hello World"
end
end
这有点无聊。我们希望我们的方法大喊大叫!但是,我们不想只是复制和重新实现该方法,我们希望重用其内部实现,而不必知道它是如何在内部实现的。我们想给它打补丁,这样这种方法的所有客户端都有喊叫行为。流行的方法是这样的:
class SomeClass
alias_method :__some_method_without_shouting :some_method
def __some_method_with_shouting
__some_method_without_shouting.upcase
end
alias_method :some_method :__some_method_with_shouting
end
在此示例中,我们使用alias_method
来创建我们正在猴子修补的方法的"备份",以便我们可以从该方法的猴子修补版本中调用它。(否则,该方法将消失。这实际上是alias_method
文档中给出的用例。
这个成语非常流行和广泛使用,以至于一些库甚至为它提供了一个实现,例如 ActiveSupport 的Module#alias_method_chain
。
但是请注意,这个成语有一些不太好的属性。一个是我们正在用所有这些_with_
和_without_
方法污染命名空间。例如,当您查看对象的方法时,您将看到所有这些方法。另一个问题是人们仍然可以直接调用旧方法,但大概我们有理由修补它,因为我们不想要旧的行为。(否则,我们可以创建一个带有新名称的方法,调用旧名称,例如shout
.)
一直有一个更好的替代方案没有被广泛使用:
class SomeClass
some_method_without_shouting = instance_method(:some_method)
define_method(:some_method) do
some_method_without_shouting.bind(self).().upcase
end
end
在这里,我们将旧方法存储在局部变量中,并使用块来定义新方法(通过Module#define_method
)。局部变量在类体末尾超出范围,因此永远无法再在任何地方访问它。但是块是闭包,它们关闭周围的词汇环境,因此传递给define_method
的块(并且只有这个块)仍然可以访问变量绑定。这样,旧的实现是完全隐藏的,并且没有命名空间污染。
但是,从 Ruby 2.0 开始,对于此方法包装有一个更好的解决方案:Module#prepend
。prepend
的美妙之处在于它是"只是继承",我们可以简单地用super
module Shouter
def some_method
super.upcase
end
end
class SomeClass
prepend Shouter
end
例如,Module#prepend
是Module#alias_method_chain
在ActiveSupport 5.0中被弃用并在5.1中删除的原因。所有这些扭曲都不再需要了。
因此,总结一下:使用alias_method
有两个主要原因:字面上创建一个别名,即同一操作的两个名称,以及为方法包装创建备份副本。第二个不再有效,也许可以说从来没有。今天,只有第一个原因是使用alias_method
的正当理由。
我认为这是来自我之前回答的一个问题,我建议使用alias_method
,所以我有一点额外的上下文来解释它在那个上下文中的使用。
在你的代码片段中,你有一些代码可以读取attr_reader :thirsty
,基本上是同名实例变量的获取者(@thirsty
def thirsty
@thirsty
end
在原始代码片段中,您有一个断言:
refute vampire.thirsty?
您还有代码只是为thirsty?
方法返回true
,这失败了您的断言。
至少有两种方法可以修改代码,以便对thirsty?
调用有效并通过断言:
创建一个调用thirsty
读取器的方法,或访问@thirsty
实例变量本身:
def thirsty?
thirsty # or @thirsty
end
另一种方法是使用alias_method
,它在功能上等价于上述。它将thirsty?
别名化为thirsty
这是一个从@thirsty
实例变量读取的attr_reader
参考我给出的另一个答案
你最好不要使用attr_reader,而只是按照Sergio在他的评论中指出的那样做:
class Vampire
def initialize(name)
@name = name
@thirsty = true
end
def thirsty?
@thirsty
end
def drink
@thirsty = false
end
end