在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. 如下 :
p Class.superclass
p Module.superclass
结果
Module
Object
其次, 定义Module时, Module也可以包含常量, Module singleton method, instance method等.
module MyMod1
NAME = "digoal.zhou"
class Myclass1
end
def method1
puts("this is MyMod1's instance method.")
end
end
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 MyMod1
def method1 # 定义instance method
puts("this is MyMod1's instance method.")
end
def self.method2 # 定义singleton method
puts("this is MyMod1's singleton method.")
end
def MyMod1.method3 # 定义singleton method
puts("this is MyMod1's another singleton method.")
end
end
其中singleton method可以通过如下方法调用 :
MyMod1.method2
MyMod1.method3
输出
this is MyMod1's singleton method.
this is MyMod1's another singleton method.
但是, 需要注意Module与Class的不同之处, Module不能创建它的object, 但是Class可以创建它的object. 因此Module的instance method不能通过创建对象来调用. 自建的Class有superclass和subclass. 而自建的Module没有superclass和subclass(Module本身是Object的subclass). 如下 :
p MyMod1.class
p Module.superclass
p MyMod1.superclass
输出
Object
Module
C:/Users/digoal/Desktop/new.rb:16:in `<main>': undefined method `superclass' for MyMod1:Module (NoMethodError)
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.
例如 :
module MyModule
GOODMOOD = "happy"
BADMOOD = "grumpy"
def greet
return "I'm #{GOODMOOD}. How are you?"
end
def MyModule.greet
return "I'm #{BADMOOD}. How are you?"
end
end
p MyModule.greet
输出
"I'm grumpy. How are you?"
变量的访问范围则和以前讲解的一样.
4. Included Modules, or Mixins
那到底要怎么调用Module 的instance method呢?
mixin, 例如使用include.
例如 :
module MyModule
x = 1
GOODMOOD = "happy"
BADMOOD = "grumpy"
def greet
return "I'm #{GOODMOOD}. How are you?"
end
def MyModule.greet
return "I'm #{BADMOOD}. How are you?"
end
end
include MyModule
p greet
p GOODMOOD
p MyModule.greet
p x # 报错, 因为x 是MyModule这个object的local variable. 不能在外部直接访问, 后面还有个例子可以说明Module的local variable带入到了当前环境, 但是不可被访问. 但是我个人认为Module的local variable不应该带入到当前环境, 即不可见为啥还要带进来.
结果
"I'm happy. How are you?"
"happy"
"I'm grumpy. How are you?"
C:/Users/digoal/Desktop/new.rb:19:in `<main>': undefined local variable or method `x' for main:Object (NameError)
如果在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
class Myclass1
include MyModule
def m1
return BADMOOD
end
end
ob1 = Myclass1.new
p ob1.greet
p ob1.m1
结果
"I'm happy. How are you?"
"grumpy"
下面是一个include多个模块的例子 :
module MagicThing
attr_accessor :power
end
module Treasure
attr_accessor :value
attr_accessor :owner
end
class Weapon
attr_accessor :deadliness
end
class Sword < Weapon # descend from Weapon
include Treasure # mix in Treasure
include MagicThing # mix in MagicThing
attr_accessor :name
end
s = Sword.new
s.name = "Excalibur"
s.deadliness = "fatal"
s.value = 1000
s.owner = "Gribbit The Dragon"
s.power = "Glows when Orcs appear"
puts(s.name) #=> Excalibur
puts(s.deadliness) #=> fatal
puts(s.value) #=> 1000
puts(s.owner) #=> Gribbit The Dragon
puts(s.power) #=> Glows when Orcs appear
下面的例子说明Module的local variable在include是被带入到当前环境, 因为如果没有带进来, no_bar应该返回的是1.
x = 1 # local to this program
module Foo
x = 50 # local to module Foo
# this can be mixed in but the variable x won't be visible
def no_bar
return x
end
def bar
@x = 1000
return @x
end
puts( "In Foo: x = #{x}" ) # this can access the module-local x
end
include Foo # mix in the Foo module
puts(x) #=> 1
puts( no_bar ) # Error: undefined local variable or method 'x'
puts(bar) #=> 1000
下面的例子说明Module object的instane variables不会被带入到当前环境, 而class variables可以带入, 并修改, 如下 :
module X
@instvar = "X's @instvar"
@@modvar = "X's @@modvar"
def self.aaa
puts(@instvar)
end
def self.bbb
puts(@@modvar)
end
end
X.aaa #=> X's @instvar
X.bbb #=> X's @@modvar
include X
p @instvar #>= nil
p @@modvar #=> X's @@modvar
class Myclass1
include X
def m1
p @instvar
end
def m2
p @@modvar
end
def m3(newvar)
@@modvar = newvar
end
end
ob1 = Myclass1.new
ob1.m1 #=> nil
ob1.m2 #=> "X's @@modvar"
ob1.m3("ob1 modify @@modvar") # 因为Module object的class可以被include到当前环境, 是可以被修改的.
X.bbb #=> ob1 modify @@modvar
下面的例子可以看出, Module中定义的方法在include到当前环境后, 它就像在当前环境定义的一样, 所以我们看到amethod里面的@insvar实际上是给当前环境的instance variable赋值, 而和Module object的instance variable没有一点关系.
module X
@instvar = "X's @instvar"
@anotherinstvar = "X's 2nd @instvar"
def amethod
@instvar = 10 # creates @instvar in current scope
puts(@instvar)
end
end
include X
p( @instvar ) #=> nil
amethod #=> 10
puts( @instvar ) #=> 10
@instvar = "hello world"
puts( @instvar ) #=> "hello world"
p( X.instance_variables ) #=> [:@instvar, @anotherinstvar]
p( self.instance_variables ) #=> [:@instvar] # 这也是在执行 amethod 后给self环境创建的一个instance variable.
5. Name Conflicts
如果两个module同时定义了一个同名的方法, 那么如果这两个module都被include了, 则后面的覆盖前面的同名方法, 例如 :
如果把include的顺序调换一下结果则是xxx
换成class variable, constants 与上面的测试结果一样 .
6. Alias Methods
module M1
def method1
return "xxx"
end
end
module M2
def method1
return "yyy"
end
end
class Myclass1
include M1
include M2
end
ob1 = Myclass1.new
p ob1.method1
输出
yyy
如果把include的顺序调换一下结果则是xxx
class Myclass1
include M2
include M1
end
ob1 = Myclass1.new
p ob1.method1
输出
xxx
换成class variable, constants 与上面的测试结果一样 .
module M1
A = "xxx"
def method1
return A
end
end
module M2
A = "yyy"
def method1
return A
end
end
class Myclass1
include M2
include M1
def m1
p A
end
end
ob1 = Myclass1.new
ob1.m1 #=> "xxx"
include M2
p A #=> "yyy"
6. Alias Methods
怎么处理重名的instance method? 可以在module的定义中使用alias. 例如
7. Mix in with Care!
module Happy
def Happy.mood
return "happy"
end
def expression
return "smiling"
end
alias happyexpression expression
end
module Sad
def Sad.mood
return "sad"
end
def expression
return "frowning"
end
alias sadexpression expression
end
class Person
include Happy
include Sad
attr_accessor :mood
def initialize
@mood = Happy.mood
end
end
p2 = Person.new
puts(p2.mood) #=> happy
puts(p2.expression) #=> frowning
puts(p2.happyexpression) #=> smiling
puts(p2.sadexpression) #=> frowning
7. Mix in with Care!
Module中可以include 其他Module, class中也可以include Module, class又可以继承其他superclass, 所以Module的出现使程序的继承关系变得很复杂, 不能滥用, 否则就连DEBUG都会成个头痛的问题. 来看一个例子, 你可能会觉得有点晕.
8. Including Modules from Files
# This is an example of how NOT to use modules!
module MagicThing # module
class MagicClass # class inside module
end
end
module Treasure # module
end
module MetalThing
include MagicThing # mixin
class Attributes < MagicClass # subclasses class from mixin
end
end
include MetalThing # mixin
class Weapon < MagicClass # subclass class from mixin
class WeaponAttributes < Attributes # subclass
end
end
class Sword < Weapon # subclass
include Treasure # mixin
include MagicThing # mixin
end
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详解 :
p $:.class
p $:
$: << "." # 追加当前目录
p $:
输出
Array
["C:/Ruby193/lib/ruby/site_ruby/1.9.1", "C:/Ruby193/lib/ruby/site_ruby/1.9.1/i386-msvcrt", "C:/Ruby193/lib/ruby/site_ruby", "C:/Ruby193/lib/ruby/vendor_ruby/1.9.1", "C:/Ruby193/lib/ruby/vendor_ruby/1.9.1/i386-msvcrt", "C:/Ruby193/lib/ruby/vendor_ruby", "C:/Ruby193/lib/ruby/1.9.1", "C:/Ruby193/lib/ruby/1.9.1/i386-mingw32"]
["C:/Ruby193/lib/ruby/site_ruby/1.9.1", "C:/Ruby193/lib/ruby/site_ruby/1.9.1/i386-msvcrt", "C:/Ruby193/lib/ruby/site_ruby", "C:/Ruby193/lib/ruby/vendor_ruby/1.9.1", "C:/Ruby193/lib/ruby/vendor_ruby/1.9.1/i386-msvcrt", "C:/Ruby193/lib/ruby/vendor_ruby", "C:/Ruby193/lib/ruby/1.9.1", "C:/Ruby193/lib/ruby/1.9.1/i386-mingw32", "."]
1.8和1.9使用require加载文件的区别, 1.8不转换相对路径为绝对路径因此
require "a"
require "./a"
将加载两次.
而1.9 会把相对路径转换成绝对路径, 因此
require "a"
require "./a"
只会加载1次.
require_relative是1.9新增的方法, 用来加载相对路径的文件.
require_relative( "testmod.rb" ) # Ruby 1.9 only
相当于
require("./testmod.rb")
require详解 :
require(name) → true or false click to toggle source
Loads the given name, returning true if successful and false if the feature is already loaded.
If the filename does not resolve to an absolute path, it will be searched for in the directories listed in $LOAD_PATH ($:).
If the filename has the extension “.rb”, it is loaded as a source file; if the extension is “.so”, “.o”, or “.dll”, or the default shared library extension on the current platform, Ruby loads the shared library as a Ruby extension. Otherwise, Ruby tries adding “.rb”, “.so”, and so on to the name until found. If the file named cannot be found, a LoadError will be raised.
For Ruby extensions the filename given may use any shared library extension. For example, on Linux the socket extension is “socket.so“ and require 'socket.dll' will load the socket extension.
The absolute path of the loaded file is added to $LOADED_FEATURES ($"). A file will not be loaded again if its path already appears in $". For example, require 'a'; require './a' will not load a.rb again.
require "my-library.rb"
require "db-driver"
值得注意的是如果文件中定义了Module, 则这些module在使用前需要先执行include. 例如 :
我有一个./rb.rb文件如下 :
另外有一个程序如下 :
另外一个要注意的是, 使用require加载文件时, 文件中的代码被逐一执行, 如果有输出的则会输出. 例如前面的rb.rb文件后面添加一行 puts("rb.rb file loaded now.") ,
那么在加载这个文件的时候会有输出
module M1
def method1
puts("this is a M1's instance method")
end
end
def method2
puts("this is a file's method")
end
另外有一个程序如下 :
$: << "."
require("./rb.rb")
include M1
method1 # 使用method1前必须先include M1
method2 # 使用method2则在require这个文件后就可以直接使用了. 原因很容易理解.
另外一个要注意的是, 使用require加载文件时, 文件中的代码被逐一执行, 如果有输出的则会输出. 例如前面的rb.rb文件后面添加一行 puts("rb.rb file loaded now.") ,
module M1
def method1
puts("this is a M1's instance method")
end
end
def method2
puts("this is a file's method")
end
puts("rb.rb file loaded now.")
那么在加载这个文件的时候会有输出
rb.rb file loaded now.
接下来介绍另一种加载文件的方法, load.
load是Kernel模块的方法, 它与require的不同之处, 使用load如果多次加载同一个文件, 它会多次执行. 而使用require多次加载同一个文件它只会加载一次.
例如rb.rb如下
使用load加载2次看看发生了什么 :
使用require则只执行了一次 :
C2 = 100
module M1
C1 = 10
def method1
puts("this is a M1's instance method")
end
end
def method2
puts("this is a file's method")
end
puts("C2 is #{C2}.")
使用load加载2次看看发生了什么 :
$: << "."
load("rb.rb")
load("rb.rb")
输出
C:/Users/digoal/Desktop/rb.rb:1: warning: already initialized constant C2
C:/Users/digoal/Desktop/rb.rb:3: warning: already initialized constant C1
C2 is 100.
C2 is 100.
使用require则只执行了一次 :
$: << "."
require("rb.rb")
require("rb.rb")
输出
C2 is 100.
另外load 还有一个wrap参数, true或false, 默认是false.
true的情况下, load的这个文件被当成一个匿名module执行, 执行完后不会把文件中的代码带入当前环境.
false则和require类似, 会把方法, 常量, 类等带入当前环境.
load(filename, wrap=false) → true click to toggle source
Loads and executes the Ruby program in the file filename. If the filename does not resolve to an absolute path, the file is searched for in the library directories listed in $:. If the optional wrap parameter is true, the loaded script will be executed under an anonymous module, protecting the calling program’s global namespace. In no circumstance will any local variables in the loaded file be propagated to the loading environment.
例如 :
rb.rb还是如下,
使用load加载这个文件, 分别选用wrap=true和false.
如果把三个注释去掉报错如下 :
使用false load
最后一点load和require的区别, load必须使用文件名全称, 包括rb后缀, 否则无法使用.
【参考】
C2 = 100
module M1
C1 = 10
def method1
puts("this is a M1's instance method")
end
end
def method2
puts("this is a file's method")
end
puts("C2 is #{C2}.")
使用load加载这个文件, 分别选用wrap=true和false.
$: << "."
load("rb.rb",true)
# p C2 # 报错
# method2 # 报错
# p M1::C1 # 报错
# include M1 # 报错
输出
C2 is 100.
如果把三个注释去掉报错如下 :
C:/Users/digoal/Desktop/new.rb:3:in `<main>': uninitialized constant C2 (NameError)
使用false load
$: << "."
load("rb.rb",false)
p C2
method2
p M1::C1
输出
C2 is 100.
100
this is a file's method
10
最后一点load和require的区别, load必须使用文件名全称, 包括rb后缀, 否则无法使用.
$: << "."
load("rb")
报错
C:/Users/digoal/Desktop/new.rb:2:in `load': cannot load such file -- rb (LoadError)
from C:/Users/digoal/Desktop/new.rb:2:in `<main>'
$: << "."
require("rb")
输出
C2 is 100.
【参考】
The Book Of Ruby
Ruby 1.9.3 API