包含模块或混入(Mixins)
对象可以通过使用 include
方法包含该模块来访问模块的实例方法。如果你要将 MyModule 包含到程序中,则该模块内的所有内容都会突然出现在当前作用域内。因此,现在可以访问 MyModule 的 greet
方法:
include MyModule
puts( greet )
注意,只会包含实例方法。在上面的示例中,已经包含了 greet
(实例)方法,但是 MyModule.greet
(模块)方法没有被包含…
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
正如它所包含的那样,greet
方法可以像使用当前作用域中的普通实例方法一样被使用…
puts( greet )
包含模块的过程也称为“混入”(mixing in) - 这解释了为什么包含的模块通常被称为 “mixins”。将模块混入到类定义中时,从该类创建的任何对象都将能够使用被混入模块的实例方法,就像它们在类本身中定义一样。
class MyClass
include MyModule
def sayHi
puts( greet )
end
end
不仅这个类的方法可以访问 MyModule 模块的 greet
方法,而且从类中创建的任何对象也是如此:
ob = MyClass.new
ob.sayHi
puts(ob.greet)
模块(Modules)可以被认为是离散的代码单元,可以简化可重用代码库的创建。另一方面,你可能更感兴趣的是使用模块作为实现多继承的替代方式。
回到我在本章开头提到的一个示例,让我们假设你有一个剑(Sword)类,它不仅是一种武器(Weapon),也是一种珍宝(Treasure)。或许 Sword 是 Weapon 类的后代(因此继承了 Weapon 的 deadliness
属性),但它也需要具有 Treasure 的属性(例如 value
和 owner
),并且这是拥有魔法(MagicThing)的精灵之剑。如果你在 Treasure 和 MagicThing 模块(modules
)而不是类(classes
)中定义这些属性,则 Sword 类将能够以“混入”(mix in)方式包含这些模块其方法或属性:
module MagicThing
attr_accessor :power
end
module Treasure
attr_accessor :value
attr_accessor :owner
end
class Weapon
attr_accessor :deadliness
end
class Sword < Weapon
include Treasure
include MagicThing
attr_accessor :name
end
Sword 对象现在可以访问 Sword 类本身,它的祖先类 Weapon,以及它的混入(mixed-in)模块 Treasure 和 MagicThing 的方法和属性:
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)
puts(s.deadliness)
puts(s.value)
puts(s.owner)
puts(s.power)
顺便提一下,无法从模块外部访问模块中作为局部变量的任何变量。即使模块内部的方法试图访问局部变量并且该方法是由模块外部的代码调用的 - 例如,当包含混入模块时,情况也是如此:
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 then 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
puts(x)
puts( no_bar ) # Error! This can't access the module-local variable
# needed by the no_bar method
puts(bar)
请注意,实例变量(instance variables)可用于混入方法(例如 bar
)。但是,即使在混入方法的当前作用域中存在具有相同名称的局部变量时,局部变量也不可用(因此,即使 x
在当前作用域中已经声明,no_bar
也无法访问名为 x
的变量)。
模块可以具有其自己的实例变量,这些变量仅仅属于模块“对象”。这些实例变量存在于模块方法的作用域内:
module X
@instvar = "X's @instvar"
def self.aaa
puts(@instvar)
end
end
X.aaa #=> "X's @instvar"
但实例对象中引用的实例变量“属于”包含该模块的作用域:
module X
@instvar = "X's @instvar"
def amethod
@instvar = 10 # creates @instvar in current scope
puts(@instvar)
end
end
include X
X.aaa #=> X's @instvar
puts( @instvar ) #=> nil
amethod #=> 10
puts( @instvar ) #=> 10
@instvar = "hello world"
puts( @instvar ) #=> "hello world"
类变量也会被混入,和实例变量一样,它们的值可以在当前作用域内重新分配:
module X
@@classvar = "X's @@classvar"
end
include X
puts( @@classvar ) #=> X's @@classvar
@@classvar = "bye bye"
puts( @@classvar ) #=> "bye bye"
你可以使用 instance_variables
方法获取实例变量的名称数组:
p( X.instance_variables )
p( self.instance_variables )