Ruby 中的 Module 同時存在 Namespace 跟 Mixin 兩種特性
Namespace 有點像是變數與 method 的 sandbox,包起來後可以避免命名上的衝突,這點與大部分程式語言相同。
Mixin 則是提供了在繼承關係外,仍有共用方法的可能性,避免產生 ”如果人可以飛,則人必須是鳥” 的情況,實現的感覺更像是引用而不是疊加(繼承),而 Module 提供了三種方式,分別是 include、prepend、extend。
Include 是常見的用法,只要在 class 內 include module,就能在物件實例化後呼叫
module A
def say
'A'
end
end
class User
include A
end
~$ User.new.say
"A"
需要注意 include 後的 module 並不是併入 class 本身,而是插入到 class 的繼承鏈當中,位置介於 superclass 與物件本身之間
~$ User.ancestors
[User, A, Object, Kernel, BasicObject]
所以當我們有多個 include 的時候就會產生順序性的問題
module A
def say
'A'
end
end
module B
def say
'B'
end
end
class User
include A
include B
end
~$ User.ancestors
[User, B, A, Object, Kernel, BasicObject]
~$ User.new.say
"B"
因為 Compiler 在繼承鏈中從左邊開始尋找 method,一旦找到就會返回,後面重複名稱的 method 自然沒有被執行的機會。
Extend 可以讓 module methods 直接在物件上呼叫,不同於 include 需要實例化後才可以使用
module A
def say
'A'
end
end
class User
extend A
end
~$ User.new.say
NoMethodError: undefined method `say'
~$ User.say
"A"
extend 不會將 module 插入物件繼承鏈當中
~$ User.ancestors
[User, Object, Kernel, BasicObject]
~$ User.new.say
NoMethodError: undefined method `say'
可以從 singleton class 找到 extend 的 module
~$ User.singleton_class.ancestors
[#<Class:User>, A, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
由於是插入到 singleton class 右方,若有定義 class method,會優先找到 class method
module A
def say
'A'
end
end
class User
extend A
def self.say
'Hi'
end
end
~$ User.say
"Hi"
Prepend 則是將 module 插入到物件本身的繼承鏈前方
module A
def say
'A'
end
end
class User
prepend A
def say
'Hi'
end
end
~$ User.new.say
"A"
繼承鏈
~$ User.ancestors
[A, User, Object, Kernel, BasicObject]
在 prepend 多個的時候也要考慮順序性問題
module A
def say
'A'
end
end
module B
def say
'B'
end
end
class User
prepend A
prepend B
def say
'Hi'
end
end
~$ User.ancestors
[B, A, User, Object, Kernel, BasicObject]
// 從最左邊開始尋找 method
~$ User.new.say
"B"
# 總結
繼承鏈由左向右開始尋找 繼承鏈插入的位置
- Include — 放到 self.class 的右邊
- Extend — 放到 self.singleton_class 的右邊
- Prepend —放到 self.class 的最左邊
Test on Ruby 2.6.5
# references
- http://rubylearning.com/satishtalim/modules_mixins.html
- https://medium.com/@leo_hetsch/ruby-modules-include-vs-prepend-vs-extend-f09837a5b073
- https://chunksofco.de/rubys-prepend-how-is-it-useful-d3bba8d11a95