文章出处: http://blog.163.com/digoal@126/blog/static/163877040201222712214285/
在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.superclassp Module.superclass结果ModuleObject
其次, 定义Module时, Module也可以包含常量, Module singleton method, instance method等.
module MyMod1NAME = "digoal.zhou"class Myclass1enddef method1puts("this is MyMod1's instance method.")endend
2. Module methods (Like Class singleton method)
Module 里面可以定义instance method也可以像Class那样定义singleton method. 例如 :
module MyMod1def method1 # 定义instance methodputs("this is MyMod1's instance method.")enddef self . method2 # 定义singleton methodputs("this is MyMod1's singleton method.")enddef MyMod1 . method3 # 定义singleton methodputs("this is MyMod1's another singleton method.")endend
其中singleton method可以通过如下方法调用 :
MyMod1 . method2MyMod1 . 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.classp Module.superclassp MyMod1.superclass输出ObjectModuleC:/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 MyModuleGOODMOOD = "happy"BADMOOD = "grumpy"def greetreturn "I'm #{GOODMOOD}. How are you?"enddef MyModule.greetreturn "I'm #{BADMOOD}. How are you?"endend
p MyModule.greet输出"I'm grumpy. How are you?"
变量的访问范围则和以前讲解的一样.
4. Included Modules, or Mixins
那到底要怎么调用Module 的instance method呢?
mixin, 例如使用include.
例如 :
module MyModulex = 1GOODMOOD = "happy"BADMOOD = "grumpy"def greetreturn "I'm #{GOODMOOD}. How are you?"enddef MyModule . greetreturn "I'm #{BADMOOD}. How are you?"endendinclude MyModulep greetp GOODMOODp MyModule . greetp 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里面一样.
例如
class Myclass1include MyModuledef m1return BADMOODendendob1 = Myclass1 . newp ob1 . greetp ob1 . m1结果"I'm happy. How are you?""grumpy"
下面是一个include多个模块的例子 :
module MagicThingattr_accessor : powerendmodule Treasureattr_accessor : valueattr_accessor : ownerendclass Weaponattr_accessor : deadlinessendclass Sword < Weapon # descend from Weaponinclude Treasure # mix in Treasureinclude MagicThing # mix in MagicThingattr_accessor :nameends = Sword.news.name = "Excalibur"s.deadliness = "fatal"s.value = 1000s.owner = "Gribbit The Dragon"s.power = "Glows when Orcs appear"puts(s.name) #=> Excaliburputs(s.deadliness) #=> fatalputs(s.value) #=> 1000puts(s.owner) #=> Gribbit The Dragonputs(s.power) #=> Glows when Orcs appear
下面的例子说明Module的local variable在include是被带入到当前环境, 因为如果没有带进来, no_bar应该返回的是1.
x = 1 # local to this programmodule Foox = 50 # local to module Foo# this can be mixed in but the variable x won't be visibledef no_barreturn xenddef bar@x = 1000return @xendputs( "In Foo: x = #{x}" ) # this can access the module-local xendinclude Foo # mix in the Foo moduleputs(x) #=> 1puts( 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 . aaaputs ( @instvar )enddef self . bbbputs (@ @modvar )endendX . aaa #=> X's @instvarX.bbb #=> X's @@modvarinclude Xp @instvar #>= nilp @@modvar #=> X's @@modvarclass Myclass1include Xdef m1p @instvarenddef m2p @@modvarenddef m3(newvar)@@modvar = newvarendendob1 = Myclass1 . newob1 . m1 #=> nilob1.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 scopeputs(@instvar)endendinclude Xp( @instvar ) #=> nilamethod #=> 10puts( @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了, 则后面的覆盖前面的同名方法, 例如 :
module M1def method1return "xxx"endendmodule M2def method1return "yyy"endendclass Myclass1include M1include M2endob1 = Myclass1 . newp ob1 . method1输出yyy
如果把include的顺序调换一下结果则是xxx
class Myclass1include M2include M1endob1 = Myclass1 . newp ob1 . method1输出xxx
换成class variable, constants与上面的测试结果一样 .
module M1A = "xxx"def method1return Aendendmodule M2A = "yyy"def method1return Aendendclass Myclass1include M2include M1def m1p Aendendob1 = Myclass1.newob1.m1 #=> "xxx"include M2p A #=> "yyy"
6. Alias Methods
怎么处理重名的instance method? 可以在module的定义中使用alias. 例如
module Happydef Happy . moodreturn "happy"enddef expressionreturn "smiling"endalias happyexpression expressionendmodule Saddef Sad . moodreturn "sad"enddef expressionreturn "frowning"endalias sadexpression expressionendclass Personinclude Happyinclude Sadattr_accessor : mooddef initialize@mood = Happy . moodendendp2 = Person . newputs ( p2 . mood ) #=> happyputs(p2.expression) #=> frowningputs(p2.happyexpression) #=> smilingputs(p2.sadexpression) #=> frowning
7. Mix in with Care!
Module中可以include 其他Module, class中也可以include Module, class又可以继承其他superclass, 所以Module的出现使程序的继承关系变得很复杂, 不能滥用, 否则就连DEBUG都会成个头痛的问题. 来看一个例子, 你可能会觉得有点晕.
# This is an example of how NOT to use modules!module MagicThing # moduleclass MagicClass # class inside moduleendendmodule Treasure # moduleendmodule MetalThinginclude MagicThing # mixinclass Attributes < MagicClass # subclasses class from mixinendendinclude MetalThing # mixinclass Weapon < MagicClass # subclass class from mixinclass WeaponAttributes < Attributes # subclassendendclass Sword < Weapon # subclassinclude Treasure # mixininclude MagicThing # mixinend
8. Including Modules from Files
前面我们都是用include来加载一个Module的, 这种用法需要定义Module在同一个源码文件里面.
因为Module一般都是重复利用的, 所以放在其他文件里面会复用性更强, 放在文件中的话我们可以使用require , require_relative , load等来加载.
使用文件来加载就涉及到文件放在什么地方, 如何找到我们要加载的文件的问题了.
例如 :
require( "./testmod.rb" )
或省略.rb后缀
require( "./testmod" ) # this works too
如果不写绝对路径的话, 那么被加载的文件需要在搜索路径或者$:定义的路径中. $:是一个Array对象, 可以追加
p $ :. classp $ :$ : << "." # 追加当前目录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 sourceLoads 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文件如下 :
module M1def method1puts("this is a M1's instance method")endenddef method2puts("this is a file's method")end
另外有一个程序如下 :
$ : << "."require ( "./rb.rb" )include M1method1 # 使用method1前必须先include M1method2 # 使用method2则在require这个文件后就可以直接使用了. 原因很容易理解.
另外一个要注意的是, 使用require加载文件时, 文件中的代码被逐一执行, 如果有输出的则会输出. 例如前面的rb.rb文件后面添加一行
puts("rb.rb file loaded now.")
,
module M1def method1puts ( "this is a M1's instance method" )endenddef method2puts ( "this is a file's method" )endputs ( "rb.rb file loaded now." )
那么在加载这个文件的时候会有输出
rb.rb file loaded now.
接下来介绍另一种加载文件的方法, load.
load是Kernel模块的方法, 它与require的不同之处, 使用load如果多次加载同一个文件, 它会多次执行. 而使用require多次加载同一个文件它只会加载一次.
例如rb.rb如下
C2 = 100module M1C1 = 10def method1puts("this is a M1's instance method")endenddef method2puts("this is a file's method")endputs("C2 is #{C2}.")
使用load加载2次看看发生了什么 :
$ : << "."load ( "rb.rb" )load ( "rb.rb" )输出C : /Users/ digoal / Desktop / rb . rb : 1 : warning : already initialized constant C2C : /Users/ digoal / Desktop / rb . rb : 3 : warning : already initialized constant C1C2 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 sourceLoads 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还是如下,
C2 = 100module M1C1 = 10def method1puts ( "this is a M1's instance method" )endenddef method2puts ( "this is a file's method" )endputs ( "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 C2method2p M1 :: C1输出C2 is 100.100this is a file 's method10
最后一点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.