我什么时候应该使用别名方法? - Ruby



我已经浏览了,但没有看到以下问题的答案:

你会使用什么别名方法?

class Vampire
attr_reader :name, :thirsty
alias_method :thirsty?, :thirsty
end

我使用一个的唯一原因是能够在我定义的任何方法中使用问号吗?我相信您不能对实例变量使用问号。

一个会使用Module#alias_method有两个原因,一个是最新的和有效的,另一个是过时的,无论如何都不是真正必要的。

第一个原因是,您只是希望有两个具有不同名称的方法执行完全相同的操作。其中一个原因可能是,对于同一操作,有两个同样广泛使用的术语,并且您希望让人们更容易编写其社区可以理解的代码。(Ruby 核心库中的一些例子是集合方法,它们的名称是来自函数式编程语言的人所熟悉的,例如mapreduce、来自 Smalltalk 编程语言家族的人,例如collectinjectselect和标准英语名称,例如find_all。另一种可能性是你构建了一个Fluent 接口,并希望它更流畅地阅读,如下所示:

play this
and that
and something_else

在这种情况下,and可以是play的别名。

另一个相关原因(我们称之为原因 1.5)是你想要实现某个协议,并且你已经有一个正确实现该协议语义的方法,但它的名称错误。让我们假设一个假设的集合框架,它有两个方法map_seqmap_nonseq。第一个执行map并保证副作用的顺序,而第二个不保证副作用的顺序,甚至可以异步、并发或并行执行映射操作。这两种方法实际上具有不同的语义,但是如果您的数据结构不适合并行化,那么您可以简单地实现map_seqmap_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#prependprepend的美妙之处在于它是"只是继承",我们可以简单地用super

module Shouter
def some_method
super.upcase
end
end
class SomeClass
prepend Shouter
end

例如,Module#prependModule#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

相关内容

  • 没有找到相关文章

最新更新