2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

什么时候使用` alias_mthod_chain`

Last updated at Posted at 2016-11-21

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

回顾它的过去

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

说到这里, 我估计很多人已经不再往下读了,而是回头去把代码里的所有的 a_m_c 给移除掉了. 忽视了关于如何正确合理使用它的有趣的讨论, 而是止步于此.

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

我们在写 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:

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?