模块¶ ↑
模块在 Ruby 中有两个用途:命名空间和 mix-in 功能。
命名空间可以用来组织代码,按包或功能划分,将通用名称与其他包的干扰隔离开来。例如,IRB 命名空间提供了 irb 的功能,防止了通用名称“Context”的冲突。
Mix-in 功能允许在多个类或模块之间共享通用方法。Ruby 自带了 Enumerable
mix-in 模块,它提供了许多基于 each
方法的枚举方法,而 Comparable
允许基于 <=>
比较方法比较对象。
请注意,模块和类之间有很多相似之处。除了 mix-in 模块的能力之外,下面对模块的描述也适用于类。
Module
定义¶ ↑
使用module
关键字创建模块。
module MyModule # ... end
可以多次重新打开模块以添加、更改或删除功能。
module MyModule def my_method end end module MyModule alias my_alias my_method end module MyModule remove_method :my_method end
重新打开模块(或类)是 Ruby 的一项非常强大的功能,但最好只重新打开您拥有的模块。重新打开您不拥有的模块可能会导致命名冲突或难以诊断的错误。
嵌套¶ ↑
模块可以嵌套。
module Outer module Inner end end
许多包创建一个单一的外部模块(或类)来为其功能提供命名空间。
您也可以使用::
定义内部模块,前提是外部模块(或类)已定义。
module Outer::Inner::GrandChild end
请注意,如果Outer
和Outer::Inner
尚未定义,这将引发NameError
。
这种风格的好处是允许作者减少缩进量。只需要一个缩进级别,而不是三个缩进级别。但是,使用这种语法而不是更详细的语法创建命名空间,常量查找的范围是不同的。
范围¶ ↑
self
¶ ↑
self
指的是定义当前范围的对象。当进入不同的方法或定义新的模块时,self
将发生变化。
常量¶ ↑
可访问的常量根据模块嵌套(用于定义模块的语法)而不同。在以下示例中,常量A::Z
可以从 B 访问,因为 A 是嵌套的一部分。
module A Z = 1 module B p Module.nesting #=> [A::B, A] p Z #=> 1 end end
但是,如果您使用::
定义A::B
而不将其嵌套在A
中,则会引发NameError
异常,因为嵌套不包含A
。
module A Z = 1 end module A::B p Module.nesting #=> [A::B] p Z #=> raises NameError end
如果常量在顶层定义,您可以使用::
在前面引用它。
Z = 0 module A Z = 1 module B p ::Z #=> 0 end end
方法¶ ↑
有关方法定义文档,请参阅方法语法文档。
Class
的方法可以直接调用。 (这有点令人困惑,但模块上的方法通常被称为“类方法”而不是“模块方法”。另请参阅 Module#module_function
,它可以将实例方法转换为类方法。)
当类方法引用常量时,它使用与在方法外部引用常量相同的规则,因为作用域相同。
在模块中定义的实例方法只有在包含时才能调用。这些方法可以通过祖先列表访问在包含时定义的常量。
module A Z = 1 def z Z end end include A p self.class.ancestors #=> [Object, A, Kernel, BasicObject] p z #=> 1
可见性¶ ↑
Ruby 有三种类型的可见性。默认值为 public
。公共方法可以从任何其他对象调用。
第二种可见性是 protected
。当调用受保护方法时,发送方必须继承定义该方法的 Class
或 Module
。否则,将引发 NoMethodError
。
受保护的可见性最常用于定义 ==
和其他比较方法,在这些方法中,作者不希望将对象的 state 公开给任何调用者,并且希望将其限制为继承的类。
以下是一个示例
class A def n(other) other.m end end class B < A def m 1 end protected :m end class C < B end a = A.new b = B.new c = C.new c.n b #=> 1 -- C is a subclass of B b.n b #=> 1 -- m called on defining class a.n b # raises NoMethodError A is not a subclass of B
第三种可见性是 private
。私有方法只能在没有接收者的拥有类内部调用,或者使用文字 self
作为接收者。如果私有方法使用除文字 self
之外的接收者调用,则将引发 NoMethodError
。
class A def without m end def with_self self.m end def with_other A.new.m end def with_renamed copy = self copy.m end def m 1 end private :m end a = A.new a.without #=> 1 a.with_self #=> 1 a.with_other # NoMethodError (private method `m' called for #<A:0x0000559c287f27d0>) a.with_renamed # NoMethodError (private method `m' called for #<A:0x0000559c285f8330>)
alias
和 undef
¶ ↑
您也可以为方法创建别名或取消定义方法,但这些操作不限于模块或类。有关文档,请参阅 杂项语法部分。
类¶ ↑
每个类也是一个模块,但与模块不同,类不能混合到另一个模块(或类)中。与模块一样,类可以用作命名空间。类还从其超类继承方法和常量。
定义类¶ ↑
使用 class
关键字创建类
class MyClass # ... end
如果您没有提供超类,您的新类将继承自 Object
。您可以使用 <
后跟类名来继承自其他类。
class MySubclass < MyClass # ... end
有一个特殊的类 BasicObject
,它被设计为一个空白类,包含最少的内置方法。您可以使用 BasicObject
创建一个独立的继承结构。有关更多详细信息,请参阅 BasicObject
文档。
与模块类似,类也可以重新打开。您可以在重新打开类时省略其超类。指定与先前定义不同的超类将引发错误。
class C end class D < C end # OK class D < C end # OK class D end # TypeError: superclass mismatch for class D class D < String end
继承¶ ↑
在类上定义的任何方法都可以在其子类中调用。
class A Z = 1 def z Z end end class B < A end p B.new.z #=> 1
常量也是如此。
class A Z = 1 end class B < A def z Z end end p B.new.z #=> 1
您可以通过重新定义方法来覆盖超类方法的功能。
class A def m 1 end end class B < A def m 2 end end p B.new.m #=> 2
如果您希望从方法中调用超类功能,请使用 super
。
class A def m 1 end end class B < A def m 2 + super end end p B.new.m #=> 3
当不带任何参数使用时,super
使用传递给子类方法的参数。要向超类方法发送任何参数,请使用 super()
。要向超类方法发送特定参数,请手动提供它们,例如 super(2)
。
您可以在子类方法中多次调用 super
。
单例类¶ ↑
对象的单例类(也称为元类或本征类)是一个仅为该实例保存方法的类。您可以使用 class << object
访问对象的单例类,如下所示
class C end class << C # self is the singleton class here end
您最常看到单例类以这种方式访问
class C class << self # ... end end
这允许在类(或模块)上定义方法和属性,而无需编写 def self.my_method
。
由于您可以打开任何对象的单例类,这意味着此代码块
o = Object.new def o.my_method 1 + 1 end
等效于此代码块
o = Object.new class << o def my_method 1 + 1 end end
这两个对象都将有一个返回 2
的 my_method
。