什么时候使用` alias_mthod_chain`


最近遇到这样一件事情, 一个哥们提交的代码里注释了说 **移除没用的 `alias_method_chian`**, 唉, 当时我有点恼火了, 我他么秀逗了吗为什么没用的东西我会在当初使用呢? 显然不是, 其实` alias_method_chain` 在 rails 圈里还是经常被误解的. 因为如果它没用它就不应该存在的.

### 回顾它的过去

其实对于` alias_method_chain` 来说没有什么新鲜的例子来的说明它的用途. 事实上通过你 google 搜索它的话, 你会得到很多关于它为主题的文章, 不过最好的一篇,我想应该属于 [Yehuda Katz两年前的一篇文章](http://yehudakatz.com/2009/03/06/alias_method_chain-in-models/) 他来自 rails 的核心代码小组. 他解释了我们不应该滥用 `alias_method_chain` 当在继承的时候,其他父类已经更加有效率的完成了同样的工作.

说到这里, 我估计很多人已经不再往下读了,而是回头去把代码里的所有的 `a_m_c` 给移除掉了. [忽视了关于如何正确合理使用它的有趣的讨论](http://yehudakatz.com/2009/03/06/alias_method_chain-in-models/#comments), 而是止步于此.

### 方法查找(都是关于祖先继承的问题)

我们在写` Rails 的插件`的时候, 经常会有很多这样的例子,就是当在讨论继承和父类不能够满足当前工作的内容的时候, 无法很容易去控制类的行为. 你可能会想其实很多时候都是通过`monkey...` 来完成的,也就是经常随意的上去给 Rails 的核心代码给打各式的 patch. 如` ActiveRecord::Base`. 然后呢,事情就会变得复杂起来, 你是否能够远离使用` alias_method_chain`, 取决于你将要试图去修改的方法它最初是如何被定义的. 它是定义在一个类里,还是定义在一个模块里, 而模块是从属于某个类的?

让我们来看个简单的例子, 一个` Person` 类,它包含了一个` Greetings` 的模块:


```

module Grettings
	def hello
		"hello"
	end
	
class Person
	include Grettings
end


p = Person.new
p.hello #=> "Hello"

```


看上去没有问题, 这个` p` 它能够调用` hello`, 我们接着让这个 `p` 更加和蔼一些:


```
module Flanderizer
	def hello
		"#{super}-diddly"
	end
end

Person.send :include, Flanderizer

flanders = Person.new

flanders.hello #=> "Hello-diddly"

```

这个看上去也和我们预想的一样, 它也是能够很好地调用我们期望的方法. 不过如果稍微修改一下呢:

```
    class Person
      def hello
        "Hello"
      end
    end

    module Flanderizer
      def hello
        "#{super}-diddly"
      end
    end

    # FOR FREEDOM!!!
    Person.send :include, Flanderizer

    flanders = Person.new

    flanders.hello # => "Hello"

```

这个时候就会又不一样的结果了, 事实上就是如果这个 person 本身已经定义` hello` 方法`, 它是不会在往下查找方法定义了,这里就是关于对象的继承体系的问题了.

### 创建家族树

所以, 我们的问题就是如何创新赋给它一个新的祖先呢?

第一个方法和很明显, 继承. 这里的` Person` 是继承自 object, 它具有 object 的所有方法,如果我们将它写作继承自` Person < MyClass` 这样它就继承自` MyClass` 了,它将不去继承自 object 或者其他的父类.


第二个方法看上去就没有那么明显易懂. 将一个模块添加一个类里, 让这个模块成为继承体系里最顶层的. 这样在第一个例子里, 一个 Person 它包含了一个 Greetings 模块, 然后又包含了 Flanderizer 模块, 最后我们查看它的祖先体系的时候看到这样的结果:

```
flanders.class.ancestors #=> [Person, Flanderizer, Greetings, Object, Kernal]
```

Person 它并没有` hello` 这个方法, 所以它完全取决于继承系统里的查找顺序和结果. 这样的结果就是在第二个例子里 我们得到的继承顺利是:


```
flanders.class.ancestors #=> [Person, Flanderizer, Ojbect, Kernal]

```

结果就是在 Person 自身里的` hello` 方法被直接调用了, 而完全`到达`不了` flanderizer`的` hello` 方法.

### alias_method_chain 出场

我们还有什么解决方法呢? 如果我们可以很好地控制 Person, Grettings, 还有 Flanderizer 的代码的话, 只要我们很好地控制他们的继承出场顺序, 就像例子1那样我们可以很实现自己的需求. 但是,让我们来假象一个场合, 就是我们不能够控制 Person 这个类的代码, 事情将会变成什么样子?  这个情况下我们能做的就是 hook 到现有的类里面,然后把原有的方法给别名了.然后把我们自己的方法给别名回去, 这样就到达了目的.


```

class Person
      def hello
        "Hello"
      end
end

module Flanderizer
  def self.included(base)
    base.class_eval do
      alias_method :hello_without_flanderizer, :hello
      alias_method :hello, :hello_with_flanderizer
    end
  end

  def hello_with_flanderizer
    "#{hello_without_flanderizer}-diddly"
  end
end

# FOR FREEDOM!!!
Person.send :include, Flanderizer

flanders = Person.new

flanders.hello # => "Hello-diddly"

flanders.class.ancestors # => [Person, Flanderizer, Object, Kernel]
```

这样就是我们真正想要的` alias_method_chain` 来做的事情. 其实很简单, 就是通过` alias` 把原来处于第一位的某方法给别名,然后自己上位. 狸猫换太子.


ref:

- https://ernie.io/2011/02/03/when-to-use-alias_method_chain/