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

When should I use an Alias Method? - Ruby

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

你会使用什么别名方法?

class Vampire
 attr_reader :name, :thirsty

 alias_method :thirsty?, :thirsty
end

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

我想这是我之前回答的一个问题,我建议使用 alias_method,所以我有一些额外的上下文来解释它在那个上下文中的用途。

在您的代码片段中,您有一段代码显示为 attr_reader :thirsty,它基本上是同名实例变量的 getter (@thirsty)

def thirsty
  @thirsty
end

在原始代码片段中,您有一个断言:

refute vampire.thirsty?

您的代码也只是为 thirsty? 方法返回了 true,但您的断言失败了。

至少有两种方法可以修改您的代码,以便对 thirsty? 的调用有效并且您的断言通过:

创建调用 thirsty reader 的方法,或访问 @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

为什么要使用 Module#alias_method 有两个原因,一个是当前有效的,另一个是过时的,无论如何都不需要。

第一个原因是您只是想使用两个名称不同的方法来完成完全相同的事情。原因之一可能是相同的操作有两个相同的 widely-used 术语,并且您想让人们更容易编写社区可以理解的代码。 (Ruby 核心库中的一些示例是集合方法,其名称对于来自函数式编程语言的人来说很熟悉,例如 mapreduce,来自 Smalltalk 家族的人编程语言的名称,例如 collectinjectselect 和标准英文名称,例如 find_all。)另一种可能性是您构建一个 Fluent Interface 并希望它阅读起来更流畅,像这样:

play this
and that
and something_else

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

另一个相关的原因(我们称之为原因 1.5)是您想要实现某个协议,并且您已经拥有一个可以正确实现该协议语义的方法,但它的名称有误。让我们假设一个假设的集合框架,它有两个方法 map_seqmap_nonseq。第一个执行map并保证side-effects的顺序,而第二个不保证side-effects的顺序,甚至可能异步、并发或并行执行映射操作.这两种方法实际上具有不同的语义,但是如果您的数据结构不适合并行化,那么您可以简单地实现 map_seq 并使 map_nonseq 成为别名。

在这种情况下,驱动程序并不是要为同一操作提供两个名称,而是要为两个名称提供相同的实现(如果这句话有意义的话:-D) .

过去使用 alias_method 的第二个主要原因与其语义的一个重要细节有关:当您覆盖或 monkey-patch 这两种方法中的任何一种时,这只会影响 那个 名字,但不影响另一个。这在过去用于 包装 方法的行为,如下所示:

class SomeClass
  def some_method
    "Hello World"
  end
end

这有点无聊。我们希望我们的方法大喊大叫!但是,我们不想只是复制和 re-implement 方法,我们希望 re-use 它的内部实现而不必知道它是如何在内部实现的。而我们要monkey-patch它,让这个方法的所有clients都有喊叫的行为。流行的做法是这样的:

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 创建一个 "backup" 我们正在 monkey-patch 的方法,这样我们就可以在我们的 monkey-patch 中调用它ed 版本的方法。 (不然方法就没了)这其实就是alias_method.

文档中给出的用例

这个习语非常流行 widely-used 以至于一些图书馆甚至为它提供了一个实现,例如ActiveSupport 的 Module#alias_method_chain.

请注意,这个成语有一些 not-so-nice 属性。一是我们正在用所有这些 _with__without_ 方法污染命名空间。例如。当您查看一个对象的方法时,您会看到所有这些方法。另一个问题是人们仍然可以直接调用旧方法,但大概我们有理由对其进行修补,因为我们不 想要 旧行为。 (否则,我们可以用一个新名称调用旧方法,例如 shout。)

总有更好的选择,但并非如此widely-used:

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)。局部变量在 class 主体的末尾超出范围,因此再也无法在任何地方访问它。但是块是闭包,它们关闭了周围的词法环境,因此该块传递给了define_method(并且这个块)仍然可以访问变量绑定。这样,旧的实现就完全隐藏了,没有命名空间污染。

但是,从 Ruby 2.0 开始,有一个更好的方法包装解决方案:Module#prependprepend的美妙之处在于它是"just inheritance",我们可以简单地使用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.

的正当理由