原文地址:http://qinfanpeng.github.io/jekyll/update/2016/06/24/the_secret_of_module_include.html 对应视频版:http://v.youku.com/v_show/id_XMTYyMzAyOTY5Mg==.html
先上段代码:
class Person
def wear
'穿衣'
end
end
class Economist < Person
end
economist = Economist.new
economist.wear # => ? ①
module Professor
def wear
"戴眼镜"
end
end
class Economist < Person
include Professor
end
economist.wear # => ?②
module Professor
def wear
"#{super},戴眼镜"
end
end
economist.wear # => ?③
module Expert
def refute_rumor
'辟谣'
end
end
module Professor
include Expert
end
economist.refute_rumor # => ?④
Economist.include Professor
economist.refute_rumor # => ?⑤
相信对于上面四处问号,我们都有自己的答案了。毫无疑问①处会返回“穿衣”,这里我们都不会错;②是返回“穿衣”还是“戴眼镜”呢?估计要是没有③处的对比的话,估计有人会回答错误,②的正确答案是“戴眼镜”;那么③的正确答案自然是“穿衣,戴眼镜”了;④处会返回“辟谣”吗?,并不会,这里会报错:No Method Error
;⑤会正确返回“辟谣”
不过前面答对与否,我们最好理解背后的原理,尤其是④处的情况。①处最好理解,调用了继承自Person
的wear
方法而已,继承体系大致如下:
下面来看②③两处处,由前面的答案可知②调用的是来自Professor
module 中的wear
方法,而非Person
Class 中的。结合②③两处,可大致得出如下继承体系:
等等,如果是这样的话,那么Professor
被另一个 class include 的时候,该怎么办呢?总不能说一个 module 有多个 super 吧。记得以前看《Ruby 元编程》的时候,里面有很多类似的图。为了节约时间就不去翻书了,直接去 Google Image 里搜索关键字:ruby include
,不难发现这张图:
“被 include 的实际是 module 的副本,而非 module 本身”,如此一来 module 被 include 无论多少次都无所谓了,因为更改的是那些对应副本的super
。所以前面的图应该改成这样才对:
再看下③处,这里我们是在Professor
被 include 后(实际上被 include 的是的Professor
的副本),用类似打开类的方式修改的Professor
,并没有修改其副本。这里正确返回“穿衣,戴眼镜”,能说明Professor
和它的副本共享了同一份方法实现,即是说拷贝的时候并没拷贝底层的方法,类似下面这样:
与③不同是④是通过在Professor
include 另外一个 moduleExpert
来修改的,而这个过程又发生在Professor
被 include 之后。这种情况下Economist
感知不到Professor
的改变。究其深层原因,结合前面的经验,不难得出以下结果:
我们索性一鼓作气画一下⑤处的情况: