领域驱动设计-软件核心复杂性应对之道(12)_将设计模式应用于模型本章的核心观点是:设计模式(来自GoF等)不仅是技术解决方案,其背后的概念也能映射到领域模型中的深层概念。当我们以一种新的视角——将设计模式同时视为“概念模式”和“技术模式”——来应用它们时,它们就能有效地指导领域建模,并提升模型的表现力和灵活性。核心梳理1. 设计模式与领域模式的区别技术设计模式(如GoF模式):通常从纯技术角度描述,解决代码层面的可复用和灵活性,如算法替换、对象创建等。领域模式(如本书前几章介绍的模式):专门在MODEL-DRIVEN DESIGN的上下文中解决领域模型的问题,其概念直接源自业务领域。新视角:当将技术设计模式应用到领域层时,必须变换一下重点。从技术角度看,它是解决代码问题的方案;从模型角度看,它代表了领域中的一个深层概念。例如,"STRATEGY"在技术上代表“可替换的算法”,在领域上可以代表“业务策略”。2. 模式:STRATEGY/POLICY(策略)定义:将算法(过程、规则)封装到独立的对象中,使它们可以互相替换,并与使用它们的客户端解耦。领域动机:在领域模型中,很多过程(如“路线查找”、“费用计算”、“折扣计算”)有不止一种合理的实现方式。如果将这些选择直接写在过程内部,过程会变得臃肿、难以理解和修改。STRATEGY将**“易变的部分”**从过程主体中分离出来,使过程本身和被替换的规则都变得清晰。示例(路线查找):"Routing Service"接收一个"Leg Magnitude Policy"作为参数("Fastest"或"Cheapest"),从而在无需修改"Routing Service"内部逻辑的情况下,切换查找最快路线或最便宜路线的算法。这使得模型中的“策略”概念被显式化。设计重点:当作为领域模式使用时,重点不在于“替换算法的能力”,而在于**“表示领域概念的能力”**。"Leg Magnitude Policy"不仅仅是一种实现技巧,它本身就是一个领域对象,是团队通用语言的一部分。3. 模式:COMPOSITE(组合)定义:将对象组织成树型结构以表示“整体-部分”的层次结构。客户可以一致地对待单个对象和组合对象。领域动机:在复杂的领域中,经常出现“由部分组成整体,部分本身又是更小部分的整体”的层次结构(如运输航线由多段航线组成,每段航线又可细分)。传统的硬编码会导致每层都需要不同的接口,增加了复杂性。COMPOSITE通过统一抽象类型,使客户代码对所有层次的对象使用相同的方式。示例(运输航线):将"Route"定义为一个抽象类型。"Leg"(航段)和"Segment"(路段的航线)都实现"Route"。一个"Segment"可以由多个"Leg"组合而成。在使用时,客户端无需区分调用的是一条"Leg"还是一个"Segment",因为两者都返回"Route"的响应。这深刻揭示了“不同级别的Route本质上都是Route”的领域概念。设计重点:关键在于行为的对称性。不仅结构上组合,在行为上(如计算总里程、获取中间停靠点)也必须对单对象和组合对象提供一致的接口。4. 为何没有介绍FLYWEIGHT原因:FLYWEIGHT通常只作为实现上的优化(共享值对象以节省内存),而不是一个概念的抽象。它不描述领域中的任何深层概念,因此不适合作为领域模式。不像COMPOSITE,其概念核心(“整体由部分构成,部分也是整体”)本身就是一个重要的领域结构。项目使用时的指导原则基于以上核心思想,在项目中“将设计模式应用于模型”时,可以遵循以下原则:1. 动机转换:从“技术复用”到“概念表达”原则:在决定是否将GoF设计模式应用到领域层时,首要的判断标准是:**该模式的“概念”是否代表了领域中的一个重要概念?**而不是“这个模式能让代码更优雅”。行动:自问清单:在应用一个设计模式前,问自己:这个模式的名字是否对应了一个领域术语?(如“价格策略”对应"PricePolicy")模式和它所连接的对象一起,是否构成了一个能被领域专家理解的、有意义的业务概念?如果去掉这个模式,改用简单的"if-else"或硬编码,业务是否会丢失一个重要的“选择”或“结构”?优先核心域:这种“概念性”应用设计模式的最高价值体现在核心域。在核心域中,一个"COMPOSITE"(如"Route")或"STRATEGY"(如"PricingPolicy")不仅仅改变了代码结构,它改变了团队对领域模型的认知。2. 对模式进行“领域化”重命名原则:当使用一个GoF模式时,不要使用其在技术书本中的名称,而应使用一个领域术语作为类名。类的命名应该使阅读代码的人能立刻理解其业务含义,而不是其技术结构。行动:用领域词替代:不要定义一个名为"FeeStrategy"的接口,应定义一个叫"FeePolicy"或"FeeCalculationRule"的接口。"Strategy"这个技术术语对领域专家没有意义,而"Policy"或"Rule"是许多业务领域中的常用词汇。命名反映意图:"LegMagnitudePolicy" 比"RouteSelectionStrategy"好得多。前者表达了“航段规模策略”的业务概念,而后者听起来像一个技术实现。保持一致性:一旦选择了与模式对应的领域术语(如"Policy"),就在整个团队和项目中保持一致,并将其纳入通用语言。例如,你可能会说“我们给客户定制了一个VIP折扣策略”。3. 使用方法:从“模式驱动”模型到“模式涌现”模型原则:设计模式通常应该作为重构的目标,而不是设计的起点。更健康的路径是:先基于领域知识设计一个朴素但正确的模型;当发现该模型中存在可替换的算法或嵌套的结构时,再将该模式引入模型。行动:先“对”再“美”:先建立一个能够准确反映业务逻辑、基于通用语言的模型。此时,它可能很朴素,使用"if-else"和循环,但它是“正确”的。观察“变化的诱因”:当发现以下动机时,就是采用STRATEGY或COMPOSITE的信号:STRATEGY:"if-else"/"switch"语句在多个地方重复出现,每次新增条件都需要修改多处代码。COMPOSITE:出现复杂的嵌套"for"循环,且对单对象和组合对象的处理逻辑中存在明显的重复规则。逐步重构:在单元测试的保护下,将自定义的朴素代码逐步重构为模式结构。例如,先将"if-else"重构为指向子类的继承关系,再提取出"Strategy"接口。4. 谨慎使用,避免过度工程原则:设计模式是解决特定复杂问题的工具,不应该成为项目的“银弹”。对一个永远不会变化、只有一个实现的简单“过程”,不应该为了用模式而用模式。过度使用设计模式会增加代码复杂度,反而破坏了“柔性设计”的目标。行动:三思而后用:如果一个过程只有一种实现,且几乎没有变化可能,直接写成方法即可,无需引入"Strategy"接口和类。YAGNI(你不会用到它)原则在这里依然有效。面向变化:模式的使用与“变化”密切相关。在评估是否使用时,要问“这个部分在未来3-6个月内有多种变化/实现的可能吗?”如果答案是“几乎不可能”,那么就保持简单。文档化:如果你决定使用某个模式(如COMPOSITE),确保在代码注释或架构文档中解释其业务动机(“因为一个Segment需要像Leg一样被处理”),而不仅仅是照搬其技术定义。总结: 在项目中“将设计模式应用于模型”的核心是赋予技术模式以业务灵魂。其指导原则是:将“是否表达领域概念”作为是否使用模式的首要判断标准;用领域术语而非技术术语命名模式元素;坚持“先有正确模型,后引入模式”的重构路径;最后,警惕过度工程,只在有明确变化和不一致的地方引入模式。