1. Module 的概念
In Ruby, modules are somewhat similar to classes: they are things that hold methods, just like classes do. However, modules can not be instantiated. I.e., it is not possible to create objects from a module. And modules, unlike classes, therefore do not have a method new.
2. Module 與 class
父子關(guān)系
在講解 module 時(shí),繞不開的是它與 class 的對(duì)比。
先看看看 class 與 module 的千絲萬(wàn)縷:
> Class.superclass
=> Module
可以看到,class 是 module 的子。
能力差別
> Class.instance_methods - Module.instance_methods
=> [:allocate, :new, :superclass,...]
- Module 是不能實(shí)例化的。所以沒有 allocate 與 new 方法
# allocate 與 new 的區(qū)別
class Test
def initialize(test=5566)
@test = test
end
end
aaa = Test.new
=> #<Test:0x007fc9228e1ff0 @test=5566>
bbb = Test.allocate
=> #<Test:0x007fc923c928d8>
- Module 是不能被 module 繼承的。所以沒有 superclass
但是 module 里可以 包含其他 module。Module 可以解決多集成的問題。
3. Module 的 mixin 用法
Mixin 為類的實(shí)例化方法
通過 include 可以將 module 的方法混入到類的實(shí)例化方法中
module Tryable
def who_am_i
"我屬於#{self.class}"
end
end
class RubyGirl
include Tryable
end
annie = RubyGirl.new.who_am_i
=> "我屬於RubyGirl"
Mixin 為類方法
通過 extend 可以將 module 的方法混入到類的類方法中
module Tryable
def who_am_i
"我屬於#{self.class}"
end
end
class RubyGirl
extend Tryable
end
RubyGirl.who_am_i
=> "我屬於Class"
也可以使用 include 的方式改寫,這樣只能是類方法,如下:
module Tryable
def self.included(base_class)
base_class.class_eval do
def self.who_am_i
"我屬於#{self.class}"
end
end
end
end
class RubyGirl
include Tryable
end
RubyGirl.who_am_i
=> "我屬於Class"
4. Module 與 ActiveSupport::Concern(關(guān)系)
Concern 集成 include 與 extend
假設(shè),我們需要在 Post & Advertisement 都同時(shí)需要以下內(nèi)容,并且實(shí)現(xiàn)是一樣的:
- scope :active
- active? instances method
- all_active class method
class Post < ActiveRecord::Base
# scopes
scope :active, lambda { |active|
where(is_active: true)
}
# instances method
def active?
is_active
end
# class method
def self.all_active
puts 'Update all data'
end
end
class Advertisement < ActiveRecord::Base
# scopes
scope :active, lambda { |active|
where(is_active: true)
}
# instances method
def active?
is_active
end
# class method
def self.all_active
puts 'Update all data'
end
end
調(diào)用方式分別為:
Post.active # scope
Post.new.active? # instance method
Post.all_active # class method
使用上面提及的 module 的應(yīng)用,及 DRY(Don't Repeat Yourself)原則,可以改為:
# 需要定義多個(gè) modules
module ActAsActivable
module ClassEval
# ClassEval 沒有 scope 的定義,無法直接引用。所以在 ActiveModel include 后通過類去動(dòng)態(tài)創(chuàng)建
def self.included(base_clazz)
base_clazz.class_eval do
scope :active, lambda { |active|
where(is_active: true)
}
end
end
end
# 示例方法 module
module InstanceMethods
def active?
is_active
end
end
# 類方法 module
module ClassMethods
def all_active
puts 'Update all data'
end
end
end
class Post < ActiveRecord::Base
include ActAsActivable::ClassEval
include ActAsActivable::InstanceMethods
extend ActAsActivable::ClassMethods
end
class Advertisement < ActiveRecord::Base
include ActAsActivable::ClassEval
include ActAsActivable::InstanceMethods
extend ActAsActivable::ClassMethods
end
也可以編寫為,這樣應(yīng)用更簡(jiǎn)便:
module ActAsActivable
def self.included(base)
base.send(:include, InstanceMethods)
base.extend ClassMethods
base.class_eval do
scope :active, lambda { |active|
where(is_active: true)
}
end
end
# 示例方法 module
module InstanceMethods
def active?
is_active
end
end
# 類方法 module
module ClassMethods
def all_active
puts 'reload all data'
end
end
end
class Post < ActiveRecord::Base
include ActAsActivable
end
class Advertisement < ActiveRecord::Base
include ActAsActivable
end
Concern 有點(diǎn)類似上面的簡(jiǎn)單寫法,并且更加簡(jiǎn)練,容易讀懂。Concern 提供了同時(shí)混入多種類型模塊的操作。
module ActAsActivable
extend ActiveSupport::Concern
included do |base|
scope :active, lambda { |active|
where(is_active: true)
}
end
module ClassMethods
def all_active
puts 'reload all data'
end
end
# instance methods
def active?
is_active
end
end
Concern 解決多層依賴問題
module Foo
# base 不會(huì)取最頂層的 class, 只有直接 mixin Foo 的,才能擁有類方法 foo_parent_method
# 所以不能通過 Bar 傳遞到 Host
def self.included(base)
base.class_eval do
def self.foo_parent_method
puts 'Hello, I am defined at module Foo'
end
end
end
end
module Bar
# mixin 后,能直接使用 Bar.foo_parent_method
include Foo
# Host include Bar后, 就會(huì)執(zhí)行 Host.foo_parent_method
def self.included(base_class)
base_class.foo_parent_method
end
end
class Host
include Foo # Host 也是需要單獨(dú) mixin,才能使用 Host.foo_parent_method
include Bar
end
或者寫作:
module Foo
# 誰(shuí) extend Foo,誰(shuí)便有了類方法 foo_parent_method
def foo_parent_method
puts 'Hello, I am defined at module Foo'
end
end
module Bar
# mixin 后,能直接使用 Bar.foo_parent_method
extend Foo
# Host include Bar后, 就會(huì)執(zhí)行 Host.foo_parent_method
def self.included(base_class)
base_class.foo_parent_method
end
end
class Host
extend Foo
include Bar
end
Host <(mixin) Bar < (mixin) Foo,為什么 Host 還是需要 mixin Foo 呢?
Bar include 的 Foo,里面動(dòng)態(tài)生成了類方法是基于 Bar 的,只能 Bar.foo_parent_method 這樣子調(diào)用。
當(dāng) Host include Bar 后,F(xiàn)oo 的類方法是無法直接突破兩層,作為 Host 直接使用,必須在 Host extend Foo,才能有 Foo 定義的類方法。
這里只是舉例子,實(shí)際可以將 Bar 改為以下內(nèi)容,Host 就不需要依賴 Foo 了,但是很難避免 Foo 同時(shí)被 Bar 和 Host 依賴的情況(如 Foo 的類方法需要?jiǎng)討B(tài)獲取 class 的信息,如 class name)。
module Bar
# mixin 后,能直接使用 Bar.foo_parent_method
extend Foo
# Host include Bar 后,只會(huì)執(zhí)行 Bar.foo_parent_method
def self.included(base_class)
self.foo_parent_method
end
end
Concern 可以有效解決這種多層依賴的問題,它的 included 里的 self 使用的是最頂層的 class。
module Foo
extend ActiveSupport::Concern
included do
class_eval do
def self.foo_parent_method
puts 'Hello, I am defined at module Foo'
end
end
end
end
module Bar
extend ActiveSupport::Concern
include Foo
included do
self.foo_parent_method
end
end
class Host
include Bar
end
注意,如果把 Foo 改成:
module Foo
extend ActiveSupport::Concern
# 誰(shuí) extend Foo,誰(shuí)便有了類方法 foo_parent_method
def foo_parent_method
puts 'Hello, I am defined at module Foo'
end
end
module Bar
extend Foo
end
module Host
include Bar
end
執(zhí)行 Host.foo_parent_method 是無效的,只能執(zhí)行 Bar.foo_parent_method
所以解決依賴的只適用于使用 include 實(shí)現(xiàn) mixin
5. 參考
《Ruby女孩(24):模組是不生孩子的!模組與類別差異及mixin介紹》
《軟件設(shè)計(jì)原則——DRY(Dont Repeat Yourself)和KISS( Keep It Simple, Stupid)》
《instance_eval 與 class_eval 差異》
《ActiveSupport::Concern 小結(jié)》