如何用服务对象模式应用迪米特法则?附代码合规性验证问询
好问题!咱们一步步来拆解你的疑问:
一、existent_order方法是否违反迪米特法则?
答案是确实有违反的嫌疑。
先回忆下迪米特法则的核心:一个对象应该只和自己的「直接朋友」打交道,不要随意操作朋友的内部细节(也就是朋友的朋友)。
看你代码里的这段链式调用:
organization.orders.with_external_id(external_id).first
这里organization是服务对象的直接朋友(初始化时传入的依赖),但orders是organization的内部集合属性,with_external_id又是这个集合的方法——相当于你直接伸手去操作朋友的私人物品,还调用了私人物品的专属方法,这就越界了。服务对象根本不需要知道organization内部有orders这个集合,也不需要知道集合有with_external_id这个查询方法,它只需要知道「能不能从这个组织里找到对应外部ID的订单」就够了。
二、如何用服务对象模式应用迪米特法则?
服务对象的核心是封装单一业务逻辑,同时要尽量减少对象间的耦合。针对你的场景,我们可以通过以下方式重构:
1. 把内部查询逻辑封装到依赖对象中
让Organization类自己负责「根据外部ID找订单」的逻辑,而不是让服务对象深入到它的内部结构。给Organization新增一个方法:
class Organization # ... 其他原有代码 def find_order_by_external_id(external_id) orders.with_external_id(external_id).first end end
2. 重构服务对象的代码
修改CreateOrderService里的existent_order方法,只和直接朋友organization做单一交互:
class CreateOrderService def initialize(organization, order_params) @organization = organization @order_params = order_params end private def not_exists? existent_order.blank? end def existent_order @existent_order ||= organization.find_order_by_external_id(external_id) end def create # some code to create an order... end attr_reader :organization, :order_params # 补充获取external_id的逻辑(假设从order_params中提取) def external_id order_params[:external_id] end end
3. 额外的设计小技巧
- 服务对象只做「流程控制」:专注于创建订单的业务流程,不要承担查询其他对象内部数据的职责
- 隐藏内部实现细节:每个对象只暴露必要的接口,比如
Organization只暴露find_order_by_external_id,而不是直接把orders集合对外抛出 - 警惕链式调用:以后再看到
a.b.c.d这种多层调用,先停下来想想是不是违反了迪米特法则,尽量拆成单一的方法调用
这样重构后,服务对象和Organization的耦合度大幅降低——哪怕以后Organization内部存储订单的方式变了(比如换集合类型、改查询逻辑),服务对象完全不需要修改,只要Organization维护好find_order_by_external_id这个接口就行。
内容的提问来源于stack exchange,提问作者Bruno Fernandes

