深入探索

模块与类

我们已经研究了模块的行为。现在我们来看看模块的本质是什么。事实证明,与 Ruby 中的大多数其它东西一样,模块是对象(object)。事实上,每个命名模块都是 Module 类的实例:

module_inst.rb
  1. module MyMod
  2. end
  3. puts( MyMod.class ) #=> Module

你无法创建命名模块的后代,因此不允许这样做:

  1. module MyMod
  2. end
  3. module MyOtherMod < MyMod
  4. end

但是,与其它类一样,允许创建 Module 类的后代:

  1. class X < Module
  2. end

实际上,Class 类本身就是 Module 类的后代。它继承了 Module 的行为并添加了一些重要的新行为 - 特别是创建对象的能力。你可以通过运行 modules_classes.rb 程序来显示此继承链以验证 Module 是 Class 的超类:

modules_classes.rb
  1. Class
  2. Module #=> is the superclass of Class
  3. Object #=> is the superclass of Module

预定义模块

以下模块内置于 Ruby 解释器中:

  1. Comparable, Enumerable, FileTest, GC, Kernel, Math, ObjectSpace, Precision, Process, Signal

Comparable 是一个 mixin 模块,允许包含类以实现比较运算符。包含类必须定义 <=> 运算符,该运算符将接收器对象与另一个对象进行比较,返回 -1,0 或 +1,具体取决于接收器对象是否小于,等于或大于另一个对象。Comparable 使用 <=> 来实现常规的比较运算符(<<===>=>)和 between? 方法。

Enumerable 是为枚举(enumeration)提供的 mix-in 模块。包含类必须提供 each 方法。

FileTest 是一个包含文件测试功能的模块;它的方法也可以从 File 类访问。

GC 模块为 Ruby 的标记和垃圾回收清除机制提供了一个接口。一些底层方法也可以通过 ObjectSpace 模块获得。

Kernel 是 Object 类包含的模块;它定义了 Ruby 的“内置”(‘built-in)方法。

Math 是一个包含了基本三角函数和超越函数功能的模块函数(module functions)的模块。它具有相同定义和名称的“实例方法”(instance methods)和模块方法。

ObjectSpace 是一个包含了与垃圾回收工具交互的例程,并且允许你使用迭代器遍历所有活动对象的模块。

Precision 是为有具体精度的数字(numeric)类提供的 mixin 模块。这里,”Precision” 表示近似的实数精度,因此,该模块不应包含在不是 Real(实数)子集的任何类中(因此它不应包含在诸如 Complex(复数)或 Matrix(矩阵)之类的类中)。

Process 是操纵进程(processes)的模块。它的所有方法都是模块方法(module methods)。

Signal 是用于处理发送到正在运行的进程的信号的模块。可用信号名称列表及其解释取决于操作系统。

以下是三个最常用的Ruby模块的简要概述…

Kernel

最重要的预定义模块是 Kernel,它提供了许多“标准”(standard)Ruby 方法,如 getsputsprintrequire。与许多 Ruby 类库一样,Kernel 是用 C 语言编写的。虽然 Kernel 实际上是“内置于”(built into)Ruby 解释器,但从概念上讲它可以被视为一个 mixed-in 模块,就像普通的 Ruby mixin 一样,它使得它的方法可以直接用于任何需要它的类。由于它混入到了 Object 类中,所有其它 Ruby 类都继承自该类,因此 Kernel 的方法是全部可访问的。

Math

math.rb

Math 模块的方法以“模块”(module)和“实例”(instance)方法的形式同时提供,因此可以通过将 Math 混入到类中或从外部通过使用模块名称,点和方法名来访问模块方法;你可以使用双冒号访问常量:

  1. puts( Math.sqrt(144) )
  2. puts( Math::PI )

Comparable

compare.rb

Comparable 模块通过将模块混入到你的类中并定义 <=> 方法,来提供一种巧妙的方式定义你自己的比较’运算符’(operators)<<===>=>。然后,你可以指定将当前对象中的某些值与其他值进行比较的规则。例如,你可能会比较两个整数,两个字符串的长度或一些更不常用的值(例如数组中字符串的位置)。我在我的示例程序 compare.rb 中选择了这种不常用的比较类型。这里使用了一个虚构的数组中的字符串索引,以便将一个人的名字与另一个人的名字进行比较。小的索引(例如位于索引 0 处的 “hobbit”)被认为是小于大的索引(例如位于索引 6 处的 “dragon”):

  1. class Being
  2. include Comparable
  3. BEINGS = ['hobbit','dwarf','elf','orc','giant','oliphant','dragon']
  4. attr_accessor :name
  5. def <=> (anOtherName)
  6. BEINGS.index[@name]<=>BEINGS.index[anOtherName]
  7. end
  8. def initialize( aName )
  9. @name = aName
  10. end
  11. end
  12. elf = Being.new('elf')
  13. orc = Being.new('orc')
  14. giant = Being.new('giant')
  15. puts( elf.name < orc.name ) #=> true
  16. puts( elf.name > giant.name ) #=> false

作用域解析

与类一样,你可以使用双冒号作用域解析运算符(scope resolution operator)来访问模块内声明的常量(包括类和其它模块)。例如,假设你有嵌套的模块和类,如下所示:

  1. module OuterMod
  2. moduleInnerMod
  3. class Class1
  4. end
  5. end
  6. end

你可以使用 :: 运算符访问 Class1,如下所示:

  1. OuterMod::InnerMod::Class1
有关类中常量的作用域解析的介绍,请参见第 2 章…

每个模块和类都有自己的作用域,这意味着可以在不同的作用域中使用单个常量名称。既然如此,你可以使用 :: 运算符在确定的作用域内指定常量:

  1. Scope1::Scope2::Scope3 #...etc

如果在常量名称的最开头使用此运算符,则会产生“打破”(breaking out)当前作用域并访问“顶级”(top level)作用域的效果:

  1. ::ACONST # refers to ACONST at „top level‟ scope

以下程序提供了作用域运算符的一些示例:

scope_resolution.rb
  1. ACONST = "hello" # We'll call this the "top-level" constant
  2. module OuterMod
  3. module InnerMod
  4. ACONST=10
  5. class Class1
  6. class Class2
  7. module XYZ
  8. class ABC
  9. ACONST=100
  10. def xyz
  11. puts( ::ACONST ) #<= this prints the „top-level‟ constant
  12. end
  13. end
  14. end
  15. end
  16. end
  17. end
  18. end
  19. puts(OuterMod::InnerMod::ACONST)
  20. #=> displays 10
  21. puts(OuterMod::InnerMod::Class1::Class2::XYZ::ABC::ACONST)
  22. #=> displays 100
  23. ob = OuterMod::InnerMod::Class1::Class2::XYZ::ABC.new
  24. ob.xyz
  25. #=> displays hello

模块函数

module_func.rb

如果你希望函数既可用作实例方法又可用作模块方法,则可以使用 module_function 方法,传入与实例方法的名称相匹配的符号(symbol)即可,如下所示:

  1. module MyModule
  2. def sayHi
  3. return "hi!"
  4. end
  5. def sayGoodbye
  6. return "Goodbye"
  7. end
  8. module_function :sayHi
  9. end

现在,sayHi 方法可以混入到一个类中并用作实例方法:

  1. class MyClass
  2. include MyModule
  3. def speak
  4. puts(sayHi)
  5. puts(sayGoodbye)
  6. end
  7. end

它可以用作模块方法,使用点符号:

  1. ob = MyClass.new
  2. ob.speak
  3. puts(MyModule.sayHi)

由于这里的 sayGoodbye 方法不是模块函数(module function),因此不能以这种方式使用:

  1. puts(MyModule.sayGoodbye) #=> Error: undefined
  2. method

Ruby 在其一些标准模块,例如 Math(在 Ruby 库文件,complex.rb 中)),中使用 module_function 来创建模块和实例方法的“匹配对”(matching pairs)。

扩展对象

你可以使用 extend 方法将模块的方法添加到特定对象(而不是整个类),如下所示:

extend.rb
  1. module A
  2. def method_a
  3. puts( 'hello from a' )
  4. end
  5. end
  6. class MyClass
  7. def mymethod
  8. puts( 'hello from mymethod of class MyClass' )
  9. end
  10. end
  11. ob = MyClass.new
  12. ob.mymethod
  13. ob.extend(A)

现在对象 ob 用模块 A 进行了扩展(extend),它可以访问该模块的实例方法 method_a

  1. ob.method_a

实际上,你可以一次用多个模块来扩展对象。这里,模块 BC 扩展了对象 ob

  1. ob.extend(B, C)

当使用包含了与对象类中方法同名的方法的模块扩展对象时,模块中的方法将替换该类中的方法。所以,我们假设 ob 用这个类来扩展…

  1. module C
  2. def mymethod
  3. puts( 'hello from mymethod of module C' )
  4. end
  5. end

现在,当你调用 ob.mymethod 时,将显示字符串 ‘hello from mymethod of module C’ 而不是之前显示的 ‘hello from mymethod of class MyClass’。

你可以通过使用 freeze 方法来“冻结”(freezing)它,以阻止对象被扩展:

  1. ob.freeze

任何进一步扩展此对象的尝试都将导致运行时错误(runtime error)。为了避免这样的错误,你可以使用 frozen? 方法来测试对象是否已被冻结:

  1. if !(ob.frozen?)
  2. ob.extend( D )
  3. ob.method_d
  4. else
  5. puts( "Can't extend a frozen object" )
  6. end