天天看点

Ruby Study 13 : Modules and Mixins

在ruby中class的superclass只能有一个, childclass可以有多个. 所以一个class如果要继承多个class的特性是不可能的.

如果有这种需求, 可以考虑把这些特性封装在module中. module可以很好的解决这类问题, 通过require或者load或者include可以把module的instance method等带入到当前环境, 也就是可以使用module中的方法等. 一个class可包含多个module, 接下来详细的讲解.

1. a module is like a class

为什么说module和class很相像呢? 

首先是module是class的superclass. 如下 :

其次, 定义module时, module也可以包含常量, module singleton method, instance method等.

2. module methods (like class singleton method)

module 里面可以定义instance method也可以像class那样定义singleton method. 例如 :

其中singleton method可以通过如下方法调用 : 

但是, 需要注意module与class的不同之处, module不能创建它的object, 但是class可以创建它的object. 因此module的instance method不能通过创建对象来调用. 自建的class有superclass和subclass. 而自建的module没有superclass和subclass(module本身是object的subclass). 如下 :

3. modules as namespaces

module 是一系列methods, classes, constants的封装, 在module内部它们在一个namespace内, 相互可以访问, 但是对module外部是隔离的.

ruby的类库里定义了一些module如, math, kernel.

the math module contains module functions for basic trigonometric and transcendental functions. (例如sqrt方法, pi常量)

kernel则包含了一些我们已经常用的chomp,chop,lambda,puts,print等方法.

详见api.

例如 :

变量的访问范围则和以前讲解的一样.

4. included modules, or mixins

那到底要怎么调用module 的instance method呢?

mixin, 例如使用include.

如果在class的定义中include module, 则可以把它的constants, instance method, class, instance variable, 带入到class中, 就好像这些是写在你定义的class里面一样.

例如

下面是一个include多个模块的例子 :

下面的例子说明module的local variable在include是被带入到当前环境, 因为如果没有带进来, no_bar应该返回的是1.

下面的例子说明module object的instane variables不会被带入到当前环境, 而class variables可以带入, 并修改, 如下 :

下面的例子可以看出, module中定义的方法在include到当前环境后, 它就像在当前环境定义的一样, 所以我们看到amethod里面的@insvar实际上是给当前环境的instance variable赋值, 而和module object的instance variable没有一点关系.

5. name conflicts

如果两个module同时定义了一个同名的方法, 那么如果这两个module都被include了, 则后面的覆盖前面的同名方法, 例如 :

如果把include的顺序调换一下结果则是xxx

换成class variable, constants与上面的测试结果一样 .

6. alias methods

怎么处理重名的instance method? 可以在module的定义中使用alias. 例如

7. mix in with care!

module中可以include  其他module, class中也可以include module, class又可以继承其他superclass, 所以module的出现使程序的继承关系变得很复杂, 不能滥用, 否则就连debug都会成个头痛的问题. 来看一个例子, 你可能会觉得有点晕.

8. including modules from files

前面我们都是用include来加载一个module的, 这种用法需要定义module在同一个源码文件里面. 

因为module一般都是重复利用的, 所以放在其他文件里面会复用性更强, 放在文件中的话我们可以使用require , require_relative , load等来加载.

使用文件来加载就涉及到文件放在什么地方, 如何找到我们要加载的文件的问题了.

例如 : 

require( "./testmod.rb" )

或省略.rb后缀

require( "./testmod" )  # this works too

如果不写绝对路径的话, 那么被加载的文件需要在搜索路径或者$:定义的路径中. $:是一个array对象, 可以追加

1.8和1.9使用require加载文件的区别, 1.8不转换相对路径为绝对路径因此

而1.9 会把相对路径转换成绝对路径, 因此

require_relative是1.9新增的方法, 用来加载相对路径的文件.

require详解 : 

值得注意的是如果文件中定义了module, 则这些module在使用前需要先执行include. 例如 : 

我有一个./rb.rb文件如下 :

另外有一个程序如下 : 

另外一个要注意的是, 使用require加载文件时, 文件中的代码被逐一执行, 如果有输出的则会输出. 例如前面的rb.rb文件后面添加一行puts("rb.rb file loaded now."),

那么在加载这个文件的时候会有输出

接下来介绍另一种加载文件的方法, load.

load是kernel模块的方法, 它与require的不同之处, 使用load如果多次加载同一个文件, 它会多次执行. 而使用require多次加载同一个文件它只会加载一次.

例如rb.rb如下

使用load加载2次看看发生了什么 : 

使用require则只执行了一次 : 

另外load 还有一个wrap参数, true或false, 默认是false.

true的情况下, load的这个文件被当成一个匿名module执行, 执行完后不会把文件中的代码带入当前环境. 

false则和require类似, 会把方法, 常量, 类等带入当前环境.

rb.rb还是如下,

使用load加载这个文件, 分别选用wrap=true和false.

如果把三个注释去掉报错如下 : 

使用false load

最后一点load和require的区别, load必须使用文件名全称, 包括rb后缀, 否则无法使用.

【参考】

the book of ruby

ruby 1.9.3 api