跳至內容 跳至搜尋

一口大小的關注點分離

我們常常遇到中等大小行為區塊而想要萃取出來的情況,但只會混入單一類別中。

萃取一個純 Ruby 物件將其封裝起來並與原始物件協同合作或委派,這通常是不錯的選擇;但若沒有額外的狀態需要封裝,或我們對父類別做出 DSL 風格的宣告,那麼引入新的合作者會導致複雜化,而不是簡化。

典型的做法是將所有內容丟進一個巨型類別中,或許附註個提示作為最不壞的方案。在不同檔案中使用模組表示要檢視全局觀點必須不斷仔細篩選。

分離小型關注點的令人不滿意方式

使用提示

class Todo < ApplicationRecord
  # Other todo implementation
  # ...

  ## Event tracking
  has_many :events

  before_create :track_creation

  private
    def track_creation
      # ...
    end
end

使用內嵌模組

有雜訊的語法。

class Todo < ApplicationRecord
  # Other todo implementation
  # ...

  module EventTracking
    extend ActiveSupport::Concern

    included do
      has_many :events
      before_create :track_creation
    end

    private
      def track_creation
        # ...
      end
  end
  include EventTracking
end

混入雜訊流放至其自己的檔案中

一旦我們的行為區塊開始突破捲動到瞭解邊界,我們便會屈服並將其移至一個獨立的檔案。在這種情況下,增加的額外負擔是合理權衡,即使它降低了我們對事情運作機制的直覺式理解。

class Todo < ApplicationRecord
  # Other todo implementation
  # ...

  include TodoEventTracking
end

引入 Module#concerning

我們安撫了混入雜訊後,出現了一種自然而低程序化的方式來分離一口大小的關注點。

class Todo < ApplicationRecord
  # Other todo implementation
  # ...

  concerning :EventTracking do
    included do
      has_many :events
      before_create :track_creation
    end

    private
      def track_creation
        # ...
      end
  end
end

Todo.ancestors
# => [Todo, Todo::EventTracking, ApplicationRecord, Object]

這個小步驟帶來了若干美好的連鎖效應。我們可以

  • 一覽我們的類別行為,

  • 整理巨型雜物抽屜類別,藉由分離他們的關注點,以及

  • 停止仰賴受保護/私有屬性來進行粗糙的「這是內部事物」模組化。

附加 concerning

concerning 支援 prepend: true 參數,它會對關注點使用 prepend 而不是 include

方法
C

執行個體公共方法

concern(topic, &module_definition)

定義關注點的低開銷捷徑。

concern :EventTracking do
  ...
end

等於

module EventTracking
  extend ActiveSupport::Concern

  ...
end
# File activesupport/lib/active_support/core_ext/module/concerning.rb, line 132
def concern(topic, &module_definition)
  const_set topic, Module.new {
    extend ::ActiveSupport::Concern
    module_eval(&module_definition)
  }
end

concerning(topic, prepend: false, &block)

定義一個新關注點並將它混入。

# File activesupport/lib/active_support/core_ext/module/concerning.rb, line 114
def concerning(topic, prepend: false, &block)
  method = prepend ? :prepend : :include
  __send__(method, concern(topic, &block))
end