Ruby-Module初探
module是什麼?
Ruby裏有一種叫module的東西,他和我們比較熟知的Class有很多類似之處,同樣是組合一堆常量、變量和方法的單元,但是module不能像類一樣被實例化。
Class被定義爲Module的子類,但Class不只是module的一個子集,Class的某些獨有的特性是module不具備的,比如繼承或派生的概念。
擴展閱讀:
那Class怎麼成爲Module子類的?
module的兩大用途
1. 命名空間(Namespace)
我們可以使用module去構建某一模組的命名空間,這一模組可能包括一些方法或屬性,甚至包括一些類,class其實也構建了命名空間,但是module比class顯得更簡潔、可控,我們可以打開module去爲其添加新的屬性、方法或類,但是這種更改是靜態或是全局的。
比如,如果我們要寫一套用於BASE64編解碼的功能模塊,因爲BASE64是一套既定的規則(算法),我們沒必要去定義一個類,然後每次要做BASE64編解碼的時候,在內存空間中去一個新建一個對象來使用類中的方法;或者到這裏我們想到了在類中定義靜態方法來避免新建動態對象,的確,在諸如Java這樣的語言中,我們習慣這樣做,但是Ruby能爲我們提供更純粹的方式,那就是定義一個module:
module Base64
def self.encode(plain_text)
# do_encoding
end
def self.decode(base64_str)
# do_decoding
end
end
編解碼是一個很泛的概念,比如還有URL編碼、圖片編碼、視頻編碼等等,現在我們定義這個模塊,就我們實現的編解碼方法劃定了一個特定命名空間;有了這個模塊,我們就可以從以下方面來靈活應用:
一方面,在想做編解碼的地方,可以用模塊加半角圓點前綴的方式(如Base64.encode(pain_text))直接調用這個模塊所提供的方法;
請注意到,我們在模塊方法的定義中都加了self.前綴,如果我們想單獨使用這個模塊而不是作爲其它模塊或類定義的一部分,這是必要的,因爲module的使用實際上和class相差無幾,而module又不能實例化,我們需要將其定義爲這個module對象(module也是對象哦!那self就是當前正在定義的這個module)的單例方法(靜態方法);當然,如果這個模塊裏的所有方法都想要有被單獨使用的能力,那我們可以通過extend的方式來簡化代碼的編寫,就像下面這樣:
module Base64
extend self
def encode(plain_text)
# do_encoding
end
def decode(base64_str)
# do_decoding
end
end
另一方面,如果我們想爲一個正在定義中的模塊或類增加BASE64的處理能力,那我們就可以通過module的第二個強大的功能來完成這個需求,這個功能就是下面我們提到的混入。
2. 混入(Mixins)
前文提到module沒有繼承和派生的概念,我們不能用繼承關鍵字去繼承一個module,如果要在一個特定Class (ConcreteClass)中去使用另一個已定義的module (ConcreteModule),我們需要使用include的方式將ConcreteModule裏定義的東西組裝到我們當前所要設計的單元中來,然後ConcreteModule裏定義的東西就好像我們直接在ConcreteClass裏定義的一樣。
# demo_include_module.rb
module ConcreteModule
ConstA = 'constant A defined in ConcreteModule'
def method_a
puts 'method_a defined in module ConcreteModule'
end
end
class ConcreteClass
include ConcreteModule
def method_b
puts 'method_b defined in class ConcreteClass'
end
end
puts ConcreteClass::ConstA
ConcreteClass.new.method_a
ConcreteClass.new.method_b
# demo_include_module.rb
module ConcreteModule
ConstA = 'constant A defined in ConcreteModule'
def method_a
puts 'method_a defined in module ConcreteModule'
end
end
class ConcreteClass
include ConcreteModule
def method_b
puts 'method_b defined in class ConcreteClass'
end
end
puts ConcreteClass::ConstA
ConcreteClass.method_a
ConcreteClass.new.method_a
執行這段Ruby代碼:
> ruby demo_include_module.rb
將會得到如下輸出:
constant A defined in ConcreteModule
method_a defined in module ConcreteModule
method_b defined in class ConcreteClass
如果我們想讓ConcreteModule中的方法變成ConcreteClass的類方法,只需將類名後面的include換爲extend來引入ConcreteModule即可。
# demo_extend_module.rb
module ConcreteModule
ConstA = 'constant A defined in ConcreteModule'
def method_a
puts 'method_a defined in module ConcreteModule'
end
end
class ConcreteClass
extend ConcreteModule
def method_b
puts 'method_b defined in class ConcreteClass'
end
end
puts ConcreteModule::ConstA
ConcreteClass.method_a
ConcreteClass.new.method_a
然後執行代碼:
> ruby demo_extend_module.rb
constant A defined in ConcreteModule
/xx/yy/demo_extend_module.rb:20:in `<top (required)>': undefined method `method_a' for #<ConcreteClass:0x2e663c8> (NoMethodError)
method_a defined in module ConcreteModule
from -e:1:in `load'
from -e:1:in `<main>'
Process finished with exit code 1
可以看到,extend引入的模塊裏的方法變成了目標類(對象)的單例方法,而且Ruby不允許我們像調用實例方法那樣去在一個new出來的對象上調用單例方法。
擴展問題:
- 如果ConcreteModule本來就extend了self,那我們再通過include的方式引入ConcreteModule會有什麼效果?
- ConcreteModule中定義的常量會以怎樣的方式進入ConcreteClass?
上文提及的include和extend就是module的混入,通過混入,我們可以引入預先定義的各種模塊,組成更強大的模塊或類,並且能夠方便地從模塊中獲得靜態方法和實例方法。
鉤子方法
我們在定義一個module時,還可以爲其定義鉤子方法,鉤子方法名以動詞的過去分詞形式出現,當模塊被以不同方式(include, extend)加入其它模塊或類時,對應的*ed(過去分詞)回調方法將得到調用,有了鉤子方法,我們可以去監控定義了鉤子方法的模塊被其它定義所引用的情況:
# demo_module_included_hook.rb
module ConcreteModuleBase
def self.included(other)
puts "#{other} included #{self}"
end
end
class ConcreteClass
include ConcreteModuleBase
end
# cc = ConcreteClass.new
執行包含以上代碼的.rb文件:
>ruby demo_module_included_hook.rb
ConcreteClass included ConcreteModuleBase
通過輸出結果我們可以看到,程序偵測到了ConcreteModuleBase被引用的情況,這裏已經體現了Ruby動態編程的些許魅力(關於Ruby動態編程,這裏暫不討論)!
除了included, extended, 我們還可以定義inherited, method_added, singleton_method_added等鉤子方法來偵測不同行爲的發生,真是很nice的一個特性。
小結
以上討論了module的基本概念,兩大用途,以及Ruby的鉤子機制怎樣去監控一個特定module是怎樣被引用或是被擴展的,這些遠不是module的全部內涵,比如模塊函數(module_function)、作用域(scope),還有module的加載機制,均未涉及。
module的更多機制、特性或是用法技巧,有待更多的討論和發掘。本次討論,我們以最後一個擴展問題來結束:
擴展問題:
Ruby中沒有專門定義接口(interface),爲什麼?