跳至内容 跳至搜索

小而精的关注点分离

我们经常会遇到一些中等规模的行为,我们希望将其提取出来,但只混合到单个类中。

提取一个普通的 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]

这一小步带来了一些美妙的连锁反应。我们可以

  • 一眼就理解我们类的行为,

  • 通过分离关注点来清理单体垃圾抽屉类,以及

  • 停止依赖 protected/private 来进行粗略的“这是内部的东西”模块化。

预先添加 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