脂肪模型重构的最佳实践

Best practice for fat model refactor

我正在努力让我的胖 User 模型不那么笨重。我正在使用值对象来表示自定义值和对它们的操作,并且坚持使用 ActiveSupport::Concerns 和模块。我读 this 作为灵感。

我这样放置辅助方法:

def is_a_wizard?
  power_level >= WIZARD_POWER
end

def just_became_a_wizard?
  power_level == WIZARD_POWER
end

进入模块,并将它们作为扩展包含在内。然而,它很难阅读和维护,我在视图和控制器中都需要它们中的一些(例如用于向导身份验证)。我应该把它们放在哪里?在使用时创建服务对象?

正如我在评论中所写,模型中的业务逻辑方法没问题。

但如果你觉得太多了,你可以考虑像

这样的东西
class User < AR::Base

  def predicates
    @predicates ||= ::User::Predicates.new(self)
  end

end

并在 /models/user/predicates.rb

class User
  class Predicates < SimpleDelegator
    def just_became_a_wizard?
      power_level == User::WIZARD_POWER
    end
  end
end

那么你可以这样做:

 user.predicates.just_became_a_wizard?

最大的好处是您的用户模型不再被大量的方法弄得乱七八糟。

最大的缺点是每次都需要调用代理对象。

您可以创建额外的 class 并在任何需要的地方使用它:

# lib/wizard_detector.rb
class WizardDetector
  def initialize(power_level)
    @power_level = power_level
  end

  def is_a_wizard?
    @power_level >= WIZARD_POWER
  end

  def just_became_a_wizard?
    @power_level == WIZARD_POWER
  end 
end

# app/models/user.rb
class User

  delegate :is_a_wizard?, :just_became_a_wizard?, to: :wizard_detector

  def wizard_detector
    @wizard_detector ||= WizardDetector.new(power_level)
  end
end

# anywhere else
WizardDetector.new(power_level_to_check).is_a_wizard?

请注意 wizard_detector 对象缓存在模型中,如果在请求流期间功率级别发生变化,这可能是有害的。更换缓存就可以了

我会重命名您示例中的方法:

#is_a_wizard?会是#wizard? (这将是 ruby 方式)

just_became_a_wizard? => new_wizard? (inexperienced_wizard?...这个不太明显)

计算机科学中只有两件难事:缓存失效和命名。

--菲尔·卡尔顿

我最近开始越来越多地使用 Concerns 来保持模型的清洁。我会为 "Wizard" 创建一个模块,并在模型和控制器的命名空间下使用 Concern 模块。如果您有那么多与向导相关的控制器方法,我个人会有点怀疑。对于视图,为什么不直接编写标准的 Helpers 呢?

当然,如果您有多个模型需要这些方法,这更有意义,但它仍然可以帮助清理单个模型。我个人并不觉得很难维护,因为我几乎从不接触这些小方法。您可能还会发现,您不仅可以将方法定义移动到关注点。

此外,您应该确保您的关注点确实是关注点,而不仅仅是您厌倦了在模型中查看的方法的通用垃圾场。