包含模块或混入(Mixins)

对象可以通过使用 include 方法包含该模块来访问模块的实例方法。如果你要将 MyModule 包含到程序中,则该模块内的所有内容都会突然出现在当前作用域内。因此,现在可以访问 MyModule 的 greet 方法:

modules2.rb
  1. include MyModule
  2. puts( greet )

注意,只会包含实例方法。在上面的示例中,已经包含了 greet(实例)方法,但是 MyModule.greet(模块)方法没有被包含…

  1. module MyModule
  2. GOODMOOD = "happy"
  3. BADMOOD = "grumpy"
  4. def greet
  5. return "I'm #{GOODMOOD}. How are you?"
  6. end
  7. def MyModule.greet
  8. return "I'm #{BADMOOD}. How are you?"
  9. end
  10. end

正如它所包含的那样,greet 方法可以像使用当前作用域中的普通实例方法一样被使用…

  1. puts( greet )

包含模块的过程也称为“混入”(mixing in) - 这解释了为什么包含的模块通常被称为 “mixins”。将模块混入到类定义中时,从该类创建的任何对象都将能够使用被混入模块的实例方法,就像它们在类本身中定义一样。

modules3.rb
  1. class MyClass
  2. include MyModule
  3. def sayHi
  4. puts( greet )
  5. end
  6. end

不仅这个类的方法可以访问 MyModule 模块的 greet 方法,而且从类中创建的任何对象也是如此:

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

模块(Modules)可以被认为是离散的代码单元,可以简化可重用代码库的创建。另一方面,你可能更感兴趣的是使用模块作为实现多继承的替代方式。

回到我在本章开头提到的一个示例,让我们假设你有一个剑(Sword)类,它不仅是一种武器(Weapon),也是一种珍宝(Treasure)。或许 Sword 是 Weapon 类的后代(因此继承了 Weapon 的 deadliness 属性),但它也需要具有 Treasure 的属性(例如 valueowner),并且这是拥有魔法(MagicThing)的精灵之剑。如果你在 Treasure 和 MagicThing 模块modules)而不是classes)中定义这些属性,则 Sword 类将能够以“混入”(mix in)方式包含这些模块其方法或属性:

modules4.rb
  1. module MagicThing
  2. attr_accessor :power
  3. end
  4. module Treasure
  5. attr_accessor :value
  6. attr_accessor :owner
  7. end
  8. class Weapon
  9. attr_accessor :deadliness
  10. end
  11. class Sword < Weapon
  12. include Treasure
  13. include MagicThing
  14. attr_accessor :name
  15. end

Sword 对象现在可以访问 Sword 类本身,它的祖先类 Weapon,以及它的混入(mixed-in)模块 Treasure 和 MagicThing 的方法和属性:

  1. s = Sword.new
  2. s.name = "Excalibur"
  3. s.deadliness = "fatal"
  4. s.value = 1000
  5. s.owner = "Gribbit The Dragon"
  6. s.power = "Glows when Orcs Appear"
  7. puts(s.name)
  8. puts(s.deadliness)
  9. puts(s.value)
  10. puts(s.owner)
  11. puts(s.power)

顺便提一下,无法从模块外部访问模块中作为局部变量的任何变量。即使模块内部的方法试图访问局部变量并且该方法是由模块外部的代码调用的 - 例如,当包含混入模块时,情况也是如此:

mod_vars.rb
  1. x = 1 # local to this program
  2. module Foo
  3. x = 50 # local to module Foo
  4. # This can be mixed in but the variable x won't then be visible
  5. def no_bar
  6. return x
  7. end
  8. def bar
  9. @x = 1000
  10. return @x
  11. end
  12. puts( "In Foo: x = #{x}" ) # this can access the „module local‟ x
  13. end
  14. include Foo
  15. puts(x)
  16. puts( no_bar ) # Error! This can't access the module-local variable
  17. # needed by the no_bar method
  18. puts(bar)

请注意,实例变量(instance variables)可用于混入方法(例如 bar)。但是,即使在混入方法的当前作用域中存在具有相同名称的局部变量时,局部变量也不可用(因此,即使 x 在当前作用域中已经声明,no_bar 也无法访问名为 x 的变量)。

模块可以具有其自己的实例变量,这些变量仅仅属于模块“对象”。这些实例变量存在于模块方法的作用域内:

inst_class_vars.rb
  1. module X
  2. @instvar = "X's @instvar"
  3. def self.aaa
  4. puts(@instvar)
  5. end
  6. end
  7. X.aaa #=> "X's @instvar"

但实例对象中引用的实例变量“属于”包含该模块的作用域:

  1. module X
  2. @instvar = "X's @instvar"
  3. def amethod
  4. @instvar = 10 # creates @instvar in current scope
  5. puts(@instvar)
  6. end
  7. end
  8. include X
  9. X.aaa #=> X's @instvar
  10. puts( @instvar ) #=> nil
  11. amethod #=> 10
  12. puts( @instvar ) #=> 10
  13. @instvar = "hello world"
  14. puts( @instvar ) #=> "hello world"

类变量也会被混入,和实例变量一样,它们的值可以在当前作用域内重新分配:

  1. module X
  2. @@classvar = "X's @@classvar"
  3. end
  4. include X
  5. puts( @@classvar ) #=> X's @@classvar
  6. @@classvar = "bye bye"
  7. puts( @@classvar ) #=> "bye bye"

你可以使用 instance_variables 方法获取实例变量的名称数组:

  1. p( X.instance_variables )
  2. p( self.instance_variables )