Ruby module 模組

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é)

ActiveSupport::Concern 的工作原理

Ruby for beginners - Modules

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容