我已经查看并没有看到以下问题的答案:
你会使用什么别名方法?
class Vampire
attr_reader :name, :thirsty
alias_method :thirsty?, :thirsty
end
我使用一个问号的唯一原因是能够通过我定义的任何方法使用问号吗?我相信您不能对实例变量使用问号。
我已经查看并没有看到以下问题的答案:
你会使用什么别名方法?
class Vampire
attr_reader :name, :thirsty
alias_method :thirsty?, :thirsty
end
我使用一个问号的唯一原因是能够通过我定义的任何方法使用问号吗?我相信您不能对实例变量使用问号。
一个会使用的原因有两个Module#alias_method
,一个是当前有效的,另一个是过时的并且实际上从来没有必要。
第一个原因是您只想拥有两个具有不同名称的方法来执行完全相同的操作。造成这种情况的一个原因可能是同一操作有两个同样广泛使用的术语,并且您希望让人们更容易编写其社区可以理解的代码。(Ruby 核心库中的一些示例是集合方法,这些方法具有来自函数式编程语言的人熟悉的名称,例如map
, reduce
,来自 Smalltalk 编程语言家族的人,例如collect
, inject
,select
和标准英文名称,例如find_all
。)另一种可能性是您构建一个流畅的界面并希望它更流畅地阅读,如下所示:
play this
and that
and something_else
在这种情况下,and
可能是play
.
另一个相关的原因(我们称之为原因 1.5)是您想要实现某个协议并且您已经有一个正确实现该协议语义的方法,但是它的名称错误。让我们假设一个假设的集合框架,它有两个方法map_seq
和map_nonseq
. 第一个执行amap
并保证副作用的顺序,而第二个不保证副作用的顺序,甚至可以异步、并发或并行执行映射操作。这两种方法实际上具有不同的语义,但是如果您的数据结构不适合并行化,那么您可以简单地实现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
.
我认为这是来自我之前回答的一个问题,我建议使用 using alias_method
,因此我对此有一些额外的上下文来解释它在该上下文中的使用。
在您的代码片段中,您有一段代码读取attr_reader :thirsty
基本上是同名实例变量的吸气剂 ( @thirsty
)
def thirsty
@thirsty
end
在原始代码片段中,您有一个断言:
refute vampire.thirsty?
您还拥有简单地true
为thirsty?
方法返回的代码,这使您的断言失败。
至少有两种方法可以修改您的代码,以便调用thirsty?
有效并且您的断言通过:
创建一个调用thirsty
读取器的方法,或访问@thirsty
实例变量本身:
def thirsty?
thirsty # or @thirsty
end
另一种方法是使用alias_method
,它在功能上等同于上述。它的别名thirsty?
是从实例变量中读取的thirsty
attr_reader
@thirsty
参考我给出的另一个答案
你可能最好不要使用 attr_reader,而是按照 Sergio 在他的评论中指出的那样做:
class Vampire
def initialize(name)
@name = name
@thirsty = true
end
def thirsty?
@thirsty
end
def drink
@thirsty = false
end
end