module與class最大的差別就是module沒有繼承概念,也就是沒有子module,另外module也不能產生instance。那module可以用來做什麼呢?
我們可以定義一些類似的class method(也就是用self.定義的method)到同一個module中,要使用這些method只能透過module來呼叫。
module A
A_CONST = 1
def self.a_method
p 'a_method'
end
class << self
def b1_method
p 'b1_method'
end
def b2_method
p 'b2_method'
end
end
def self.c_method
a_method
end
def self.d_method
p 'd_method'
end
private_class_method :d_method
def self.e_method
d_method
end
def f_method
f_method
end
end
p A::A_CONST # *1
A.a_method # *2
A.b1_method # *3
A.b2_method # *3
A.c_method # *4
#A.d_method # *5 # private method `d_method' called for A:Module (NoMethodError)
A.e_method # *6
#A.f_method # *7 # undefined method `f_method' for A:Module (NoMethodError)
mixin的meta-programming一個重要的技巧,也就是在不修改原本class的定義的原則下,新增或是改變class的method。
第一種mixin的方法是在class中include module來加入新的method(注意這裡就不是class method而是一般的instance method):
class A
def a_method
p 'a_method'
end
end
module B
def b_method
p 'b_method'
end
end
p A.ancestors # [A, Object, Kernel, BasicObject] # *1
class A
include B
end
p A.ancestors # [A, B, Object, Kernel, BasicObject] # *2
a = A.new
a.a_method # "a_method"
a.b_method # "b_method" # *3
A.ancestors
的結果為[A, Object, Kernel, BasicObject]
。A.ancestors
的結果為[A, B, Object, Kernel, BasicObject]
。你可以include多個module,當module之中有相同名稱的method時,後include會蓋掉前include的method:
class A
def a_method
p 'a_method'
end
end
module B
def b_method
p 'b_method_in_B'
end
end
module C
def b_method
p 'b_method_in_C'
end
end
p A.ancestors # [A, Object, Kernel, BasicObject]
class A
include B
include C
end
p A.ancestors # [A, C, B, Object, Kernel, BasicObject] # *1
a = A.new
a.a_method # "a_method"
a.b_method # "b_method_in_C" # *2
A.ancestors
的結果為[A, C, B, Object, Kernel, BasicObject]
,如果有相同名稱的method同時存在在不同的module,會根據ancestors的結果,左邊會蓋掉右邊的method,也就是C會蓋掉B的method。a.b_method
會呼叫module C的b_method
。可是使用include會遇到一個問題,也就是如果想要override的是class的method,用include的方式會失敗:
class A
def a_method
p 'a_method_in_A'
end
end
module B
def a_method
p 'a_method_in_B'
end
end
class A
include B
end
p A.ancestors # [A, B, Object, Kernel, BasicObject] # *1
a = A.new
a.a_method # "a_method_in_A" # *2
A.ancestors
的結果為[A, B, Object, Kernel, BasicObject]
。用include的方式,被include的module都會在原本class的右邊,也就是include的module是無法蓋掉原本class相同名稱的method。a.a_method
最後還是會呼叫定義在class A的a_method
。為了解決上面的問題,在ruby 2.0之後導入了另一種mixin module的方式,叫作prepend。下面的例子只將include改成prepend,但就可以達到利用module來修改class的method。
class A
def a_method
p 'a_method_in_A'
end
end
module B
def a_method
p 'a_method_in_B'
end
end
class A
prepend B
end
p A.ancestors # [B, A, Object, Kernel, BasicObject] # *1
a = A.new
a.a_method # "a_method_in_B" # *2
A.ancestors
的結果為[B, A, Object, Kernel, BasicObject]
。用prepend的方式,被pprepend的module都會在原本class的左邊,也就是prepend的module會蓋掉原本class相同名稱的method。a.a_method
最後會呼叫定義在module B的a_method
。最後,你可以在module中的method加上public, protected與private這些visiablity(預設是public),當module被mixin到class中,就相當於將public, protected與private的method加入到class中。
module B
public
def public_method
p 'public_method'
end
protected
def protected_method
p 'protected_method'
end
private
def private_method
p 'private_method'
end
end