Wednesday, October 29, 2008

(6)模块与名称空间

Ruby中提供了模块的概念,你可以使用module关键字来定义一个模块。模块类似于类,但是模块不能像类那样可以继承,你也不可能想类那样去实例化一个模块。类是模块的子类,这意味着所有的类都是模块,但是模块不是类。
模块可以被用于把一系列相关的方法放到一起,这样你就可以避免重载了Ruby核心库中的方法或者类,就是说你有了自己的名称空间,下面是一个例子,比如我们现在有两个方法用于编码和解码64位字节,我们觉得没必要专门定义两个类来维护着两个方法,那么我们可以把它们放在一个名称空间里:
module Base64
def self.encode
end

def self.decode
end
end

除了用self关键字外,我们还可以像定义类方法那样直接用模块名作为前缀:
module Base64
def Base64.encode
end

def Base64.decode
end
end




# 下面是调用这个模块内方法的示例
text = Base64.encode(data)
data = Base64.decode(text)

当然我们还可以把一组相关的类、方法、常量等都放在一个模块里,这样更加清晰:
module Base64
DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

class Encoder
def encode
DIGITS
end
end

class Decoder
def decode
Base64.helper
end
end

# 为每个类提供一个助手类
def Base64.helper
end
end
对于上面的模块,DIGITS可以在两个类中直接方法,无需加模块名前缀,但是如果方法helper方法就必须要加上模块名前缀了。
模块的另一个重要的作用是“插入”(mixin)功能。你可以在模块中定义一些列的方法,然后在其它模块或类中用到这个模块中方法的时候,通过include 方法来将模块包含进当前的模块或者方法。需要注意的是你不能把一个类插入到另一个类中。比如我们上面定义的模块,你可以在一个类中包含它,实际上include是一个Ruby的核心方法而是不是一个关键字,所以你可以向调用方法那样使用它,如果你有多个模块需要导入,不同,模块之间可以用逗号分开,也可以用括号包含所有要导入的模块,下面的两种方式是一样的:
class Point
include Comparable,OtherModule
end

class Point
include(Comparable,OtherModule)
end

说到这里我想再谈谈继承,前面我们说过Ruby中不允许多重已成,也就是说你的子类只能有一个父类。如果你是一个C++程序员你可能要问为什么Ruby不提供像C++那样的多重继承。实际上你可以通过include多个模块的方式模拟多重继承,因为如果你在类中实现了一个和模块中同名的方法,那么你最终调用的是你自己定义的类方法,而不是模块中的同名方法,这在某种意义上模拟了多重继承。
另一个要注意的地方是,假设我们现在包含了两个模块A和B,这两个模块中存在重名的方法printa,那么如果我们在类C中调用这个方法printa的时候,会调用哪个模块中的方法?答案是取决于你include模块的顺序,如果A在B的前面,那么就调用A中的printa方法而不是b中的。

No comments: