Ruby 探秘模块混入 (include Module) 背后的故事

qinfanpeng · 发布于 2016年06月27日 · 最后由 so_zengtao 回复于 2016年08月26日 · 3642 次阅读
3790
本帖已被设为精华帖!

原文地址: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;⑤会正确返回“辟谣”

不过前面答对与否,我们最好理解背后的原理,尤其是④处的情况。①处最好理解,调用了继承自Personwear方法而已,继承体系大致如下:

下面来看②③两处处,由前面的答案可知②调用的是来自Professormodule中的wear方法,而非PersonClass中的。结合②③两处,可大致得出如下继承体系:

等等,如果是这样的话,那么Professor被另一个class include的时候,该怎么办呢?总不能说一个module有多个super吧。记得以前看《Ruby元编程》的时候,里面有很多类似的图。为了节约时间就不去翻书了,直接去Google Image里搜索关键字:ruby include,不难发现这张图:

“被include的实际是module的副本,而非module本身”, 如此一来module 被include无论多少次都无所谓了,因为更改的是那些对应副本的super。所以前面的图应该改成这样才对:

再看下③处,这里我们是在Professor被include后(实际上被 include的是的Professor的副本),用类似打开类的方式修改的Professor,并没有修改其副本。这里正确返回“穿衣,戴眼镜”,能说明Professor和它的副本共享了同一份方法实现,即是说拷贝的时候并没拷贝底层的方法,类似下面这样:

与③不同是④是通过在Professorinclude另外一个moduleExpert来修改的,而这个过程又发生在Professor被 include之后。这种情况下Economist感知不到Professor的改变。究其深层原因,结合前面的经验,不难得出以下结果:

我们索性一鼓作气画一下⑤处的情况:

小结

  1. 任何时候include module都include的是对应的副本,而非module本身。这是由于include会改变继承体系,如果直接include module本身的话,就使得该module难以再被其他地方include了,而每次都include module的副本则不会有这样的问题。
  2. module和它被include的副本共享同一份方法实现,因而直接修改方法,会反映到已经include的地方去。
  3. 通include另一个module B的方式修改一个已经被include了的 module A,不会体现到include A 的地方,因为B并没有被include A的副本中去。

参考资料

  1. 《Ruby Under a Microscope》(对应中文版《Ruby原理剖析》由张汉东先生翻译,应该不久就可以和大家见面了。如果说《Ruby元编程》让我们了解很多Ruby高级用法的话,那么《Ruby原理剖析》则在更深层次上系统地阐述了这些高级用法背后的原理)
  2. 《Ruby元编程(第2版)》
共收到 11 条回复
23196

让我想到自己之前在处理一个问题( 传送门)的时候也是没有用数据本身,而是用了数据clone出来的副本。但一直报错,逐一排查发现是没法 data = self.clone。最后一怒之下直接 data = self ,然后对data进行操作。

不过感觉 data = self 和 data = self.clone 并没有太大的差别,两种赋值后data变量最终都是作为一个副本存在的。

但最后并没有深入探究到底为毛没法self.clone,惭愧之.....学习了!

3790

#1楼 @catherine 😅 ,我也是带着问题去思考,收获多一些。

26329

lz图是用什么工具画的 看得我爱不释手~

1107 jasl 将本帖设为了精华贴 06月28日 20:13
3790

#3楼 @mulderlover 用的是 Chrome 的离线应用(免费),貌似也有 web 版的。

8345

樓主說得很清晰👏,《Ruby under a microscope》值得一讀

18852

对于这个问题,《Effective Ruby》第六条有详细说明

9800

ruby 的 module 比 python 可调戏性高到不知道哪里去了。。。。

4594

#7楼 @freefishz 是的,我也刚看过,结合ruby的singleton class也是很好理解的。

8972

include 的module 会变成一个匿名单列类,加入被include的继承体系中,与之对应的是perpend

E809c2

3 和 4 讲的很好 Economist 感知不到 Professor 的改变 会不会是在 append_features 之类的地方动了手脚

也就是说在 module 里面定义新的方法 和 通过 include Module 引入的方法有不同

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册