深入探索
模块与类
我们已经研究了模块的行为。现在我们来看看模块的本质是什么。事实证明,与 Ruby 中的大多数其它东西一样,模块是对象(object)。事实上,每个命名模块都是 Module 类的实例:
module MyMod
end
puts( MyMod.class ) #=> Module
你无法创建命名模块的后代,因此不允许这样做:
module MyMod
end
module MyOtherMod < MyMod
end
但是,与其它类一样,允许创建 Module 类的后代:
class X < Module
end
实际上,Class
类本身就是 Module
类的后代。它继承了 Module 的行为并添加了一些重要的新行为 - 特别是创建对象的能力。你可以通过运行 modules_classes.rb 程序来显示此继承链以验证 Module 是 Class 的超类:
Class
Module #=> is the superclass of Class
Object #=> is the superclass of Module
预定义模块
以下模块内置于 Ruby 解释器中:
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 方法,如 gets
,puts
,print
和 require
。与许多 Ruby 类库一样,Kernel 是用 C 语言编写的。虽然 Kernel 实际上是“内置于”(built into)Ruby 解释器,但从概念上讲它可以被视为一个 mixed-in 模块,就像普通的 Ruby mixin 一样,它使得它的方法可以直接用于任何需要它的类。由于它混入到了 Object 类中,所有其它 Ruby 类都继承自该类,因此 Kernel 的方法是全部可访问的。
Math
Math 模块的方法以“模块”(module)和“实例”(instance)方法的形式同时提供,因此可以通过将 Math 混入到类中或从外部通过使用模块名称,点和方法名来访问模块方法;你可以使用双冒号访问常量:
puts( Math.sqrt(144) )
puts( Math::PI )
Comparable
Comparable 模块通过将模块混入到你的类中并定义 <=>
方法,来提供一种巧妙的方式定义你自己的比较’运算符’(operators)<
,<=
,==
,>=
和 >
。然后,你可以指定将当前对象中的某些值与其他值进行比较的规则。例如,你可能会比较两个整数,两个字符串的长度或一些更不常用的值(例如数组中字符串的位置)。我在我的示例程序 compare.rb 中选择了这种不常用的比较类型。这里使用了一个虚构的数组中的字符串索引,以便将一个人的名字与另一个人的名字进行比较。小的索引(例如位于索引 0 处的 “hobbit”)被认为是小于大的索引(例如位于索引 6 处的 “dragon”):
class Being
include Comparable
BEINGS = ['hobbit','dwarf','elf','orc','giant','oliphant','dragon']
attr_accessor :name
def <=> (anOtherName)
BEINGS.index[@name]<=>BEINGS.index[anOtherName]
end
def initialize( aName )
@name = aName
end
end
elf = Being.new('elf')
orc = Being.new('orc')
giant = Being.new('giant')
puts( elf.name < orc.name ) #=> true
puts( elf.name > giant.name ) #=> false
作用域解析
与类一样,你可以使用双冒号作用域解析运算符(scope resolution operator)来访问模块内声明的常量(包括类和其它模块)。例如,假设你有嵌套的模块和类,如下所示:
module OuterMod
moduleInnerMod
class Class1
end
end
end
你可以使用 ::
运算符访问 Class1,如下所示:
OuterMod::InnerMod::Class1
每个模块和类都有自己的作用域,这意味着可以在不同的作用域中使用单个常量名称。既然如此,你可以使用 ::
运算符在确定的作用域内指定常量:
Scope1::Scope2::Scope3 #...etc
如果在常量名称的最开头使用此运算符,则会产生“打破”(breaking out)当前作用域并访问“顶级”(top level)作用域的效果:
::ACONST # refers to ACONST at „top level‟ scope
以下程序提供了作用域运算符的一些示例:
ACONST = "hello" # We'll call this the "top-level" constant
module OuterMod
module InnerMod
ACONST=10
class Class1
class Class2
module XYZ
class ABC
ACONST=100
def xyz
puts( ::ACONST ) #<= this prints the „top-level‟ constant
end
end
end
end
end
end
end
puts(OuterMod::InnerMod::ACONST)
#=> displays 10
puts(OuterMod::InnerMod::Class1::Class2::XYZ::ABC::ACONST)
#=> displays 100
ob = OuterMod::InnerMod::Class1::Class2::XYZ::ABC.new
ob.xyz
#=> displays hello
模块函数
如果你希望函数既可用作实例方法又可用作模块方法,则可以使用 module_function
方法,传入与实例方法的名称相匹配的符号(symbol)即可,如下所示:
module MyModule
def sayHi
return "hi!"
end
def sayGoodbye
return "Goodbye"
end
module_function :sayHi
end
现在,sayHi
方法可以混入到一个类中并用作实例方法:
class MyClass
include MyModule
def speak
puts(sayHi)
puts(sayGoodbye)
end
end
它可以用作模块方法,使用点符号:
ob = MyClass.new
ob.speak
puts(MyModule.sayHi)
由于这里的 sayGoodbye
方法不是模块函数(module function),因此不能以这种方式使用:
puts(MyModule.sayGoodbye) #=> Error: undefined
method
Ruby 在其一些标准模块,例如 Math(在 Ruby 库文件,complex.rb 中)),中使用 module_function
来创建模块和实例方法的“匹配对”(matching pairs)。
扩展对象
你可以使用 extend
方法将模块的方法添加到特定对象(而不是整个类),如下所示:
module A
def method_a
puts( 'hello from a' )
end
end
class MyClass
def mymethod
puts( 'hello from mymethod of class MyClass' )
end
end
ob = MyClass.new
ob.mymethod
ob.extend(A)
现在对象 ob
用模块 A
进行了扩展(extend),它可以访问该模块的实例方法 method_a
:
ob.method_a
实际上,你可以一次用多个模块来扩展对象。这里,模块 B
和 C
扩展了对象 ob
:
ob.extend(B, C)
当使用包含了与对象类中方法同名的方法的模块扩展对象时,模块中的方法将替换该类中的方法。所以,我们假设 ob
用这个类来扩展…
module C
def mymethod
puts( 'hello from mymethod of module C' )
end
end
现在,当你调用 ob.mymethod
时,将显示字符串 ‘hello from mymethod of module C’ 而不是之前显示的 ‘hello from mymethod of class MyClass’。
你可以通过使用 freeze
方法来“冻结”(freezing)它,以阻止对象被扩展:
ob.freeze
任何进一步扩展此对象的尝试都将导致运行时错误(runtime error)。为了避免这样的错误,你可以使用 frozen?
方法来测试对象是否已被冻结:
if !(ob.frozen?)
ob.extend( D )
ob.method_d
else
puts( "Can't extend a frozen object" )
end