脂肪模型重构的最佳实践
Best practice for fat model refactor
我正在努力让我的胖 User
模型不那么笨重。我正在使用值对象来表示自定义值和对它们的操作,并且坚持使用 ActiveSupport::Concern
s 和模块。我读 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 呢?
当然,如果您有多个模型需要这些方法,这更有意义,但它仍然可以帮助清理单个模型。我个人并不觉得很难维护,因为我几乎从不接触这些小方法。您可能还会发现,您不仅可以将方法定义移动到关注点。
此外,您应该确保您的关注点确实是关注点,而不仅仅是您厌倦了在模型中查看的方法的通用垃圾场。
我正在努力让我的胖 User
模型不那么笨重。我正在使用值对象来表示自定义值和对它们的操作,并且坚持使用 ActiveSupport::Concern
s 和模块。我读 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 呢?
当然,如果您有多个模型需要这些方法,这更有意义,但它仍然可以帮助清理单个模型。我个人并不觉得很难维护,因为我几乎从不接触这些小方法。您可能还会发现,您不仅可以将方法定义移动到关注点。
此外,您应该确保您的关注点确实是关注点,而不仅仅是您厌倦了在模型中查看的方法的通用垃圾场。