模块

模块在 Ruby 中有两个用途:命名空间和混入功能。

命名空间可用于按包或功能组织代码,将常用名称与来自其他包的干扰区分开。例如,IRB 命名空间为 irb 提供了功能,可防止常用名称“Context”发生冲突。

混入功能允许在多个类或模块之间共享常用方法。Ruby 提供了 Enumerable 混入模块,该模块提供了许多基于 each 方法的枚举方法;Comparable 则允许基于 <=> 比较方法比较对象。

请注意,模块和类之间有许多相似之处。除了混入模块的能力外,下面对模块的描述也适用于类。

模块定义

使用 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

请注意,如果 OuterOuter::Inner 未被定义,这将引发 NameError

这种风格的好处是可以减少缩进量。只需一级缩进,而非三级。但是,使用此语法创建命名空间时,常量查找的范围与使用更冗长的语法不同。

Scope

self

self 指的是定义当前作用域的对象。进入不同的方法或定义新模块时,self 会发生变化。

Constants

可访问常量因模块嵌套(使用何种语法定义模块)而异。在以下示例中,由于 A 是嵌套的一部分,因此可以在 B 中访问常量 A::Z

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

方法

有关方法定义文档,请参阅 方法语法文档

类方法可以直接调用。(这有点令人困惑,但模块上的方法通常被称为“类方法”而不是“模块方法”。另请参见 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。调用受保护方法时,发送方必须继承定义该方法的类或模块。否则将引发 NoMethodError

受保护可见性最常用于定义 == 和其他比较方法,作者不希望将对象的状态暴露给任何调用者,并希望仅将其限制为继承的类。

这是一个例子

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>)

aliasundef

您还可以为方法设置别名或取消定义方法,但这些操作不限于模块或类。有关文档,请参阅 杂项语法部分

每个类也是一个模块,但与模块不同,类不能混入到另一个模块(或类)中。像模块一样,类可以用作命名空间。类还可以继承其超类的方法和常量。

定义类

使用 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 可以在子类方法中调用任意次数。

Singleton

对象的单例类(也称为元类或特征类)是一个仅保存该实例方法的类。您可以使用 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

两个对象都将有一个返回 2my_method