模組

模組在 Ruby 中有兩個用途,命名空間和混合功能。

命名空間可用於依套件或功能來組織程式碼,以將常見名稱與其他套件的干擾分開。例如,IRB 命名空間提供 irb 的功能,可防止「Context」這個常見名稱發生衝突。

混入功能允許在多個類別或模組間共用常見方法。Ruby 附帶 Enumerable 混入模組,它提供許多基於 each 方法的列舉方法,而 Comparable 則允許根據 <=> 比較方法來比較物件。

請注意,模組和類別之間有許多相似之處。除了混入模組的能力之外,以下模組的說明也適用於類別。

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

請注意,如果尚未定義 OuterOuter::Inner,這將會引發 NameError

這種樣式的好處是允許作者減少縮排量。只需要一個縮排層級,而不是 3 個。但是,使用此語法建立命名空間的常數查詢範圍與使用更詳細的語法不同。

範圍

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 內部定義 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。呼叫受保護方法時,傳送者必須繼承定義該方法的 ClassModule。否則,將會引發 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 類別

物件的單例類別(也稱為元類別或 eigenclass)是一個只為該實例持有方法的類別。您可以使用 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