模組¶ ↑
模組在 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
請注意,如果尚未定義 Outer
和 Outer::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
。呼叫受保護方法時,傳送者必須繼承定義該方法的 Class
或 Module
。否則,將會引發 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>)
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
可以像您在子類別方法中一樣,呼叫多次。
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
兩個物件都將有一個傳回 2
的 my_method
。