传递方法、通用对象与事件特定对象的选型困惑
中介者模式中服务依赖传递的方案抉择分析
我在使用中介者模式时遇到了服务依赖传递的抉择问题:DAO类封装了针对数据库实体(表或列,按复杂度拆分)的通用操作,服务类依赖DAO实现业务逻辑,且我刻意避免直接把DAO传入中介者——这样做是为了方便单元测试和后续的依赖扩展。现在纠结于以下四种传递方案的选型:
方案1:传递完整服务对象
- 实现方式:直接将完整的服务实例传入中介者
- 示例代码:
const turn = new ServerTurnMediator( new DiceService(DiceDAO), new PlayerMoneyService(PlayerMoneyDAO) )
- 优劣分析:
- 优势:实现成本最低,代码简洁,无需额外封装工作
- 劣势:明确违反最小权限原则——中介者仅需服务的部分方法,却拥有了服务的全部能力,既增加了不必要的耦合,也存在误调用非必要方法的风险;后续服务新增方法时,中介者可能无意识依赖新方法,破坏封装性。
方案2:传递绑定后的特定方法
- 实现方式:从服务实例中提取所需方法,通过
bind绑定实例后传入中介者 - 示例代码:
const playerMoneyService = new PlayerMoneyService(PlayerMoneyDAO); const methodFromPlayerMoneyService = playerMoneyService.addMoneyToPlayer.bind(playerMoneyService); const turn = new ServerTurnMediator( new DiceService(DiceDAO), methodFromPlayerMoneyService )
- 优劣分析:
- 优势:严格遵循最小权限原则,中介者仅能获取所需方法,耦合度最低;单元测试时可直接传入模拟函数,灵活性极强
- 劣势:若需多个方法,需重复执行绑定操作,代码会变得琐碎冗余;传递的孤立方法失去了服务对象的上下文语义,后续维护时难以识别方法归属,可读性差。
方案3:创建事件特定的服务对象
- 实现方式:为中介者的特定场景创建专用服务类,这类服务仅包含中介者需要的方法,通过继承或组合复用原有服务逻辑,再将完整的专用服务传入中介者
- 示例代码:
const turn = new ServerTurnMediator( new DiceService(DiceDAO), new PlayerMoneyIncreaserService(PlayerMoneyDAO) )
- 优劣分析:
- 优势:完全符合单一职责原则,专用服务职责明确,仅处理特定场景业务;中介者获取的是语义清晰的服务对象,可读性与维护性俱佳;复用逻辑时可通过组合规避继承的弊端
- 劣势:会增加类的数量,项目规模较大时可能出现大量细粒度服务类,提升代码管理成本;若多个中介者需要类似服务,可能产生重复的专用类。
方案4:DAO层构建对应继承/组合结构
- 实现方式:在DAO层按业务场景拆分,创建专用DAO子类或组合类,让服务对象依赖这些专用DAO,再将服务传入中介者
- 优劣分析:
- 优势:将业务场景粒度下放到DAO层,服务类可更专注于业务逻辑,DAO职责也更细分;后续替换DAO实现时,专用DAO的接口更贴合业务,适配成本更低
- 劣势:DAO层复杂度会上升,拆分过度可能导致DAO类数量爆炸;若业务场景变化频繁,DAO层的调整会影响上层服务,反而可能升高耦合度。
各方案适用场景及重构时机
- 方案1:适用于小型项目或快速原型开发,优先保障开发效率,耦合风险可控的场景。
重构时机:项目规模扩大,服务类方法增多,出现中介者误调用非必要方法的情况,或单元测试因服务依赖过多变得困难时。 - 方案2:适用于中介者仅需服务1-2个方法,且后续不会新增依赖的场景。
重构时机:需传递的方法超过3个,代码开始冗余,或维护时无法清晰识别方法所属上下文时,应重构为方案3。 - 方案3:适用于中大型项目,对代码可读性、可维护性要求高,且业务场景划分清晰的情况。
重构时机:专用服务类出现大量重复逻辑,或类数量过多导致管理困难时,可通过组合通用服务复用逻辑,或合并相似场景的专用服务。 - 方案4:适用于DAO层本身逻辑复杂,且不同业务场景对数据库操作差异较大(如部分场景仅需读操作,部分需复杂写操作)的情况。
重构时机:DAO层拆分导致重复代码过多,或上层服务所需DAO功能无法通过现有专用DAO满足时,应考虑用组合替代继承,或合并相似的专用DAO。
内容的提问来源于stack exchange,提问作者Kevin Greetham
相关产品推荐
相关产品推荐

