# booklist **Repository Path**: longlongint/booklist ## Basic Information - **Project Name**: booklist - **Description**: 放一些自己已经读过但还想再温习的书,或者现在在读的书,或者计划读的数目 - **Primary Language**: C - **License**: Not specified - **Default Branch**: ddd - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-03-12 - **Last Updated**: 2025-08-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 2. 交流与语言的使用 ### 2.1 Ubiquitous language 灵活、蕴含丰富知识的设计, 需要一种通用、共享的团队语言, 以及对语言的不断试验. Ubiquitous language(通用语言)的词汇包括类和主要操作的名称. 语言中的术语用来讨论模型中已经明确的规则, 或模型上的高级组织原则. 语言还可加入领域模型的模式名称以丰富语言. 模型之前关系的描述是 UbLg 的必要组合规则, 词和短语的意义反映了模型的语义. 把模型作为语言的支柱, 确保在交流中使用 UbLg, 画图写文档讲话也要用UI. 尝试不同方法消除难点, 然后重构, rename class,function,module 以保持模型一致, 解决交谈中的术语混淆. UI的更改就是对模型的更改. UbLg 是以非代码形式呈现的设计的主要载体, 包括把整个系统组织在一起的大尺度结构、定义不同系统模型关系的界限上下文, 及模型和设计中使用的其他模式. ### 2.2 大声地 建模 改善模型的最佳方式之一就是通过对话, 大声说出模型变化中的各种结构, 不完善的地方容易被听出来. UI补充: 讨论系统时要结合模型. 使用模型元素和它的交互来描述场景, 按照模型允许的方式将概念结合. 找到简单的表达方式, 并将新想法应用到图和代码. ### 2.3 一个团队, 一种语言 业务专家要与技术人员一起参与领域模型的构建, 不然没法知道抽象是否合理, 领域专家深入理解需求, 他们应该要能理解模型, 发现模型的不合理. 领域专家可以通过使用模型对象来走查场景. 领域专家来说明验收测试. 几乎不存在现成的知识可以充分说明一个app, 所以需求随着项目前进而演变. 精华后的UI来重新组织需求是演变的一部分. 语言多样性是必要的, 但领域专家与开发不能存在分歧. ### 2.4 文档和图 UML 图擅长传达对象的关系, 也擅长表现交互, 但没法给出概念定义. 草图可以反映讨论的变化, 毕竟 UML 是统一建模语言. 不要期望用UML图表示整个模型或设计, 因为不可能细致到没有遗漏, 且细节过多会导致 `只见树木, 不见森林`, 再且 UML 图无法传达模型所表示的概念含义, 无法传达对象应该做哪些事情(注解说明可解决). 图是一种沟通和解释的手段, 简洁小图可以实现这个目标, 综合大图反而失去了沟通解释的能力, 它将读者淹没在细节中, 且缺乏目的性. 我们应该使用简化图, 只包含对象模型的重要概念(理解设计至关重要). 设计细节在代码体现, 互补的图和文档能引导人注意力放到核心点上, 自然语言的讨论能填补含义上的细微差别. 通常以图为主, 文本注释为辅, 也可文本为主, 精挑简图为辅. #### 2.4.1 书面设计文档 评估文档的总体原则: 1. 文档应当作为代码和口头交流的补充: 代码做设计文档容易把读者淹没在细节里, 虽然代码行为非常明确, 但行为背后的意义很难传达. 文档不该重复表示代码已明确传达的细节. 文档应该着重说明含义, 以便人们能深入理解大尺度结构, 并将注意力放到核心元素. 文档可以澄清设计意图. 2. 文档应该鲜活并保持最新: 图显示了将概念形式化和简化为对象模型的过程中所做的取舍, 它可以是临时的, 看起来不正式反而利于交流. 设计文档最大价值在于解释模型的概念, 在代码细节中指引方向. 设计文档必须深入项目活动, 如果设计文档中用到的术语不在出现在讨论和代码, 那就失效了. 不管是太复杂还是没有关注足够重要的主题, 都会让人们阅读时索然无味. 如果文档不再负担重要作用, 单纯靠意志和纪律保持更新就是浪费精力. Ubiquitous Language 可让其他相关文档也更简洁和明确. 当领域模型反映与业务最相关的知识时, app的需求成为模型的内部场景, 而 UbLg 可直接用 model-driver-design 方式描述. 最后规格说明更简单, 因为不必传达模型背后的隐含业务知识. `文档减到最少, 且主要来补充代码和口头交流`, 可避免文档与项目脱节, 并根据 UbLg 的演变来选选择需要更新的与项目活动紧密交互的文档. #### 2.4.2 完全依赖可执行代码的情况 XP等社区选择几乎完全依赖可执行代码及其测试, 好代码表达能力强但传递信息不确保准确, 代码实际行为不变但方法名可能产生歧义或误导或过时. 断言很严格但代码组织方式表达得未必严格. 消除差异可用诸如申明式设计, 让程序元素用途陈述决定实际行为. ### 2.5 解释型模型 对设计起推动作用的模型是领域的一个视图. 为实现最小化模型, 驱动软件开发过程的技术模型必须经过严格精简. 解释型模型可包含提供上下文的领域方面(上下文用于澄清范围更窄的模型). 解释型模型可以一定自由度为特殊主题定制更强表达力的风格, 不同方式不同角度的解释有助于人们更好的学习. 解释型模型最好不要是对象模型, 不使用UML可以避免人们错误的关联软件设计. 解释型模型与驱动设计模型有对应关系但不完全类似, 为免混淆, 需要记住区别. ## 3. 绑定模型和实现 如果模型不能直接帮助开发可运行的软件, 那么这种纸上谈兵的模型又有什么意义呢? 领域驱动设计要求模型不仅能够指导早期的分析工作, 还应该成为设计的基础. ### 3.1 模式: model-driven design 程序设计和实现过程中总会发现关键知识点, 细节问题则出人意料的层出不穷, 这让纯粹的分析模型甚至无法起到理解的作用. 无论什么原因, 软件设计如果缺乏概念, 那软件充其量就是机械化产品 -- 只有功能, 但无法解释操作的原因. `如果程序设计或其核心没有和领域模型对应, 那模型就没有价值, 软件正确性也得怀疑. 另外, 模型和设计功能之间过于复杂的对应关系也难以理解和维护. 若分析与设计有严重分歧, 那分析和设计过程所获得的知识就无法彼此共享. ` 分析一定要抓住领域内的基础概念, 用通俗方式描述. 设计工作需要指定一套编程工具创建的组件, 以使项目在目标部署环境中运行. model-driven design 不再将分析模型和程序设计分离开, 而是期望找一种兼备的单一模型. 期望程序中每个对象都反映模型中所描述的相应概念, 这个要求很高, 因为它必须满足两个完全不同的目标. 领域抽象有很多方法, 解决app的问题也有很多设计方法, 因此绑定模型和程序设计切实可行. 不能因为技术考虑而削弱分析功能, 也不能只反映领域概念而舍弃软件设计原则. 模型和设计绑定需要在分析程序和设计阶段都发挥良好作用. `模型必须对程序实现实用, 必须忠实描述领域关键概念. ` `软件的设计必须忠实反映领域模型, 以便体现二者明确的关系. 反复检查并修改模型以便更自然的实现模型. 此外, 模型还必须支持健壮的 UbLg` `从模型中获取程序设计和基本职责分配的术语. 让代码忠实表述模型, 代码改变可能会是模型的改变, 且势必影响接下来相应的项目活动. ` `完全依赖模型的实现通常需要支持建模范式的软件开发工具和语言, 如面向对象编程. ` 要想代码能有效的描述模型需要用到程序设计和实现的技巧. 知识消化人员需要研究模型的各个选项, 并将它们转化为实用软件元素. 于是软件开发就变成了不断精化模型、设计和代码的统一过程. ### 3.2 建模范式和工具支持 model-driven design 要发挥作用, 一定要严格保证模型与设计的一致性. 要实现这种严格一致性, 必须要由软件工具支持的建模范式, 在程序中直接创建模型中对应的概念. 用过程语言编写的软件具有复杂的函数, 这些函数基于预先制定的执行路径连接在一起, 而不是通过领域模型中的概念联系进行连接的. ### 3.3 揭示主旨: 为什么模型对用户至关重要 除了分析模型和设计模型的分离问题, 还应该考虑用户模型和设计/实现模型的一致性问题. ### 3.4 模式: HANDS-ON Modeler `软件开发就是设计`. 虽然开发团队中的每个成员都有自己的职责, 但是将分析、建模、设计和编程工作过度分离会对 MODEL-DRIVEN DESIGN 产生不良影响. 作者给了一个只做设计的失败经验: 1. 模型的一些意图在传递过程丢失了. 模型的整体效果受细节影响很大. 2. 模型与程序实现及技术互相影响, 作者无法获得这种反馈. 3. 虽然作者有类似项目的技术实践经验, 但不参与开发经历就无法工作. `如果编码人员认为自己没必要对模型负责, 或不知道怎么让模型为程序服务, 那模型就和程序没有任何关系了. 开发人员如果没有意识改变代码意味着改变模型, 那重构不但不会增强模型的作用, 反而削弱模型的效果.` `如果建模人员不参与程序实现, 那对程序实现的约束就没有切身的感受. ` `model-driven design 的两个基本要素: 模型要支持有效的实现并抽象出关键的领域知识. ` 如果分工阻断了设计人员与开发人员的协作, 他们将无法转达实现 model-driven design 的种种细节, 经验丰富的设计人员也将不能将自己的知识技术传递给开发人员. `HANDS-ON Modeler`翻译为 亲身实践的建模者. `任何参与建模的技术人员, 不管在项目中的主要职责是什么, 都必须花时间了解代码. 任何负责修改代码的人员则必须学会用代码来表达模型. 每一个开发人员都必须不同程度地参与模型讨论并且与领域专家保持联系. 参与不同工作的人都必须有意识地通过UBIQUITOUS LANGUAGE与接触代码的人及时交换关于模型的想法. ` MODEL-DRIVEN design 利用模型来为应用程序解决问题. 项目组通过知识消化将大量杂乱无章的信息提炼成实用的模型. 而MODEL-DRIVEN design 将模型和程序实现过程紧密结合. UBIQUITOUSLANGUAGE 则成为开发人员、领域专家和软件产品之间传递信息的渠道. 最终的软件产品能够在完全理解核心领域的基础上提供丰富的功能. 如上所述, MODEL-DRIVEN DESIGN 的成功离不开详尽的设计决策. # 第二部分 模型驱动设计的构造块 本书的软件设计风格主要遵循 "职责驱动设计", 也利用了 "契约式设计" 思想. ## 第4章 分离领域 ### 4.1 模式: Layered architecture 后面再搞清楚这些抽象名词: 领域 模型元素 系统 对象 领域对象 系统功能 领域概念 软件技术概念 典型分层模型如: 用户界面层-应用层-领域层-基础设施层 [分层模型](./picture/layeredArchitecture.png) 领域有关的代码分散在其他代码中会导致走读变得异常困难, 测试也困难. 各个活动涉及大量逻辑和技术, 程序本身必须简单明了, 否则就无法理解. 要做到这点需要`将关注点分离`, 同时维护系统内部复杂的交互关系. 根据软件行业的经验惯例, 普遍使用 layered architecture(分层架构), Layered architecture `基本原则是层中的任何元素都仅依赖本层元素或下层元素`, 向上的通信必须通过间接方式进行. 大多数成功的架构使用下面4个概念层的某种变体: |layered|description| |:--|:--| |用户界面层|显示信息解释指令| |应用层|定义要完成的任务, 并指挥表达领域概念的对象来解决问题, 是与其他系统的应用层进行交互的必要渠道. 应用层要尽量简单, 不含业务规则或知识, 只为下一层的领域对象协调任务, 分配工作, 使之相互协调| |领域层(或模型层)|表达业务概念, 业务状态信息以及业务规则, 尽管保存业务状态的技术细节由基础设施层实现, 但反映业务情况的的状态是由本层控制并使用的, 领域是业务软件的核心| |基础设施层|为上面各层提供通用的技术能力, 为应用层传递消息, 为领域层提供持久化规则, 为用户界面层绘制屏幕组件等, 基础设施层还能通过架构框架来支持4个层次的交互模式| note: 将领域层分离出来才是实现 model-driven design 的关键. 给复杂的应用程序划分层次, 在每一层内分别进行设计, 使其具有内聚性并且只依赖于它的下层... 分层价值在于每层只代表程序的特定方面, 这种限制让每个方面的设计都更内聚, 更易解释. Layered architecture 尽管种类繁多, 但大多数成功架构使用下面4个概念层的某种变体: 为使软件简洁且与模型一致, 不管实际情况如何复杂, 必须运用 `建模和设计的最佳实践`. 好的设计决策能使模型和程序紧密结合, 相互促进. 本书设计风格遵循 "职责驱动设计" 原则, 也利用了 "契约式设计" 思想. 不过实际项目可能无法完全适用, 需要折中选择而又不脱离正确轨道. 好的领域模型是一门艺术, 模型中各元素的实际设计和实现则相对系统化. 把领域设计和`软件系统中其他关注点`分离会使设计与模型关系更清晰. 根据模型特征定义模型元素使元素意义更加鲜明, 对元素用已验证的模式有助于创建易实现的模型. #### 4.1.1 将各层关联起来 连接各层同时不影响分离带来的好处, 是很多模式存在的目的所在. 各层之间松散连接, 层与层依赖只能是单向的. 上层到下层通过调用公共接口, 下层到上层需要另一种通信机制, 如回调或 observers 模式. 最早将用户界面与层和领域层连接的是 model-view-controller(MVC, 模型-视图-控制器)框架. #### 4.1.2 架构框架 略 ### 4.2 领域层是模型的精髓 现在大部分软件系统都采用 layered architecture, 只是分层方案存在不同. 领域模型是一系列概念的集合, 领域层则是领域模型及与其直接相关的设计元素的表现, 由业务逻辑的设计和实现组成. 在 model-driven-design 中, 领域层的软件构造反映了模型的概念. ### 4.3 模式: the samrt ui "反模式" 略 ### 4.4 其他分离方式 14 章会介绍如 bounded context 和 anticorruption layer 等 ## 5. 软件中所表示的模型 想要在不削弱模型驱动设计能力的前提下对实现做出折中, 需要重新组织基本元素, 需要将模型与实现细节联系起来. 本章讨论侧重模型本身, 但仍考察具体模型选择与实现问题之间的关系, 我们侧重区分3种模型元素: `Entity, Value object, Service`. entity 与 value object 的区别: 一个对象用来表示具有连续性和标识的事物, 还是用于描述某种状态的属性. 领域中还有些方面适合用动作或操作来表示, 比用对象更清楚, 这方面最好用 service 来表示. 注: service 是应客户端请求来完成某事. ### 5.1 关联 对象之间的关联让建模与实现之间的交互更为复杂。模型中每个可遍历的关联, 软件中都要有同样属性的机制。 现实生活有大量的多对多关联, 这会让实现和维护变得复杂, 且很少能表示出关系的本质。至少有三种方法使关联易于控制: 1. 规定一个遍历方向 2. 添加一个限定符, 以便减少多重关联 3. 消除不必要的关联 尽可能的对关系进行约束非常重要。双向关联意味着只有将两个对象放在一起考虑才能理解它们。所以在不要求双向遍历时可以指定一个遍历方向以减少互相依赖。 坚持将关联限定为领域所倾向的方向, 可提高关联的表达力并简化实现, 还可突出剩下的双向关联的重要性。当双向关联式领域的一个语义特征或程序的功能要求双向关联时就需要保留它, 以表达这些需求。 ### 5.2 模式: ENTITY (又称 reference object) 很多对象不是通过属性定义, 而是通过连续性和标识定义的。 对象的建模可能把我们的注意力引到对象的属性上, 但实体的基本概念是`一种贯穿整个声明周期的的抽象的连续性.` 一些对象主要不是由属性定义, 实际上表示一条 "标识线"(A thread of identity), 这条线跨越时间, 且常常经历不同的表示。 -- 比如5岁的你和50岁的你是同一个你. Entity 可以是任何事物, 只要满足两个条件
1. 在整个声明周期中具有连续性 2. 它的区别并不是对用户非常重要的属性 并不是所有对象都是具有有意义`标识`的ENTITY, 如java语言特性让每个实例都能通过==操作符来标识。标识是Entity的一个微妙的、有意义的属性。 #### 5.2.1 Entity 建模 ENTITY最基本的职责是确保连续性, 以使行为清楚可预测(虽然对象的属性和行为也很重要)。 保持实体简练是实现"清楚可预测"的关键, 不要将注意力放在属性和行为上, 摆脱这些末节, 抓住Entity的本质, 尤其是用来`识别、查找、匹配对象的特征`, 只添加那些对概念至关重要的行为和这些行为所必需的属性。此外, 应该将行为和属性转移到与核心实体关联的其他对象中(有些可能是ENTITY, 有些可能是VALUE OBJECT) -- 除了标识问题之外, 实体往往通过协调其关联对象的操作来完成自己的职责. [与标识有关的属性留在Entity内](./picture/entity.png) #### 5.2.2 设计标识操作 每个 Entity 都必须有建立标识的操作方式, 以区分其他对象. ### 5.3 value object 用于描述领域的某个方面而本身没有概念标识的对象称为 Value Object(值对象), 它实例化后用来表示一些设计元素, 只关心是什么而不关心是谁。 #### 5.3.1 设计 Value object 我们不关心使用 value object 的是哪个实例, 设计value object 时可以选择复制、共享、或保持 value object 不变。 为了安全的共享对象, 脱离所有者控制的对象可以作为不可变对象来传递。 复制和共享哪个划算取决于现实环境, 虽然复制会导致系统被大量对象阻塞, 但共享会减慢分布式系统的速度。 以下情况最好用共享:
1. 节省数据库空间或减少对象数量是一个关键要求 2. 通信开销很低 3. 共享对象被严格限定为不可变 保持 value object 不可变可以极大简化实现, 并确保共享和引用传递的安全性, 也符合值的意义。属性值发生改变时应该用一个不同的 value object, 而不是修改现有的value object, 但下面的情况需要考虑让value object 可变:
1. value 频繁变更 2. 创建或删除对象的开销大 3. 替换(非修改)将打乱集群 4. value 共享不多, 或共享不会提高集群性能, 或其他技术原因 再次强调: value 是可变时, 就不能共享它, 无论是否可变, 在可能的情况下都要将他们设计为不可变。 这是一条一般规则。 #### 5.3.2 设计包含 value object 的关联 entity 和 value object 的关联的使用情况类似, 越少越好, 越简单越好。 entity 之间的双向关联很难维护, value object 之间的关联完全没有意义。 我们应尽量完全清除value object 之间的双向关联。 ### 5.4 模式: service 有时设计会包含一些特殊的操作, 概念上不属于任何对象, 无法放到 entity 或 value object 中, 它本质上讲是一些活动或动作而非事物, 不过因为建模范式的原因, 我们要想办法划归到对象的范畴。 `service 作为接口提供的一种操作`, 在模型中是独立的, 不像 entity 和 value object 具有封装的状态, 是技术框架中一种常见的模式, 也能在领域模型中使用。 service 强调的是与其他对象的关系, 只定义能为客户做什么, 往往以一个活动来命名, 而非 entity 来命名, 是动词而非名词。 service 应该有定义的职责, 且应作为领域模型的一部分来定义, 操作名称应该来自 ubiquitous language, `参数和结果都应该是领域对象`。 好的 service 有下面三个特征:
1. 与领域有关的概念不是 entity 或 value object 的一个自然组成部分 2. 接口是根据领域模型的其他元素定义的 3. 操作是无状态的(任何客户都可以使用service的任何实例, 不用关心历史状态) service 执行时将使用或修改全局访问信息(有副作用), 但service 不保持影响自身行为的状态。 总结: 当领域中的重要过程或转换操作不是 entity 或 value object 的自然职责时, 应在模型中添加一个独立的接口操作, 声明为 service。 定义接口要用模型语言, 并确保接口名称是 ubiquitous language, service 应该是无状态的。 #### 5.4.1 service 与孤立的领域层 应用层的 service 负责通知的设置, 领域层的 service 负责确定是否满足临界值。 很多领域或应用层的 service 是在 entity 和 value object 的基础上建立的, 行为类似将领域的潜在功能组织起来以执行某种任务的脚本。entity 和 value object 往往由于粒度太细无法提供对领域层功能的便捷访问。 将 service 划分到各个层中: |层|服务| |:--|:--| |应用层|资金转账应用服务
1. 获取输入(xml请求)
2. 发送消息给领域层服务,要求其执行
3. 监听确认消息
4. 决定使用基础设施service来发送通知| |领域层|资金转账领域服务
1. 与必要的account 和 ledger 对象进行交互, 执行相应的借入贷出操作
2. 提供结果的确认| |基础设施层|发送通知服务
1. 按照应用程序的指示发送电子邮件, 信件和其他信息| #### 5.4.2 粒度 上节讨论的是将一个概念建模为 service 的表现力, 但 service 还有控制领域层接口粒度的功能, 避免客户端与 entity 和 value object 耦合. 大型系统中, 中等粒度,无状态的 service 更容易被复用, 因为简单的接口背后封装了重要的功能, 但另一方面, 细粒度的对象可能导致分布式系统的消息传递效率低下。 由于应用层负责对领域对象行为进行协调, 因此细粒度的领域对象可能把领域知识泄露到应用层中, 导致应用层不得不处理复杂细致的交互。引入领域层服务有利于保持接口的简单性, 便于客户端控制并提供多样化的功能。它提供了一种在大型或分布式系统中便于对组件进行打包的中粒度功能, 且有时service是表示领域概念最自然的方式。 #### 5.4.3 对 service 的访问 与service的访问机制相比, 分离特定职责的设计决策意义更大。 ### 5.5 模式: module(也叫 package) (看起来像java里的概念, 太抽象了) 略 ### 5.6 建模范式 #### 5.6.1 对象范式流行的原因(豆包总结) 对象范式优势:在简单与复杂间平衡,易理解,能捕获领域知识且有工具支持。成熟且广泛采用,有完善设施工具,便于集成,开发者社区成熟,多数人员熟知。 不成熟范式风险:举例 10 年前某项目采用前沿技术(如面向对象数据库)遇数据库容量等问题,虽经专家解决仍严重影响项目。 现状对比:如今面向对象技术成熟,有众多解决方案和工具;其他建模范式或难掌握,或设施不完善,不适合多数项目。 使用建议:多数项目以对象技术为核心,但某些领域不适用,不应局限于此。 #### 5.6.2 对象世界中的非对象 1. 领域模型与范式的关系:领域模型不一定是对象模型,像用 Prolog 语言实现的 MODEL - DRIVEN DESIGN,其模型由逻辑规则和事实构成。模型范式为思考领域提供方式,领域模型由范式塑造,进而能用支持对应建模风格的工具实现。 2. 项目中不同范式的应用情况:项目无论选择哪种主要模型范式,领域内部分内容可能更适配其他范式。当仅有个别元素适合其他范式时,开发人员可接受蹩脚对象以维持整个模型的一致性;而当领域主要部分明显属于不同范式,应采用适合各部分的范式分别建模,并用混合工具集实现。不同范式间的依赖情况各异,例如有些是简单的封装调用(如仅一个对象调用复杂数学计算),有些则是复杂的交互依赖(如对象交互依赖于某些数学关系)。 3. 非对象组件集成的动机与挑战:基于上述情况,将业务规则引擎或工作流引擎等非对象组件集成到对象系统中,开发人员便能以最恰当的风格对特殊概念建模,况且多数系统本身就需运用非对象技术基础设施(最常见的是关系数据库)。不过,采用不同范式后,构建一个内聚的模型难度加大,不同支持工具的共存也更为复杂。这种情况下,即便混合设计更需要 MODEL - DRIVEN DESIGN,开发人员在软件中却难以辨认出内聚模型,导致其被忽视 。 #### 5.6.3 在混合范式中坚持使用MODEL-DRIVEN DESIGN 1. 规则引擎与对象范式:在面向对象应用开发项目中常混合使用其他技术,如`规则引擎`。对象范式缺乏表达规则及交互语义,虽可将规则建模为对象,但全局规则难应用。规则引擎提供自然、声明式规则定义,能有效融合规则与对象范式,逻辑范式是对象范式的有力补充。 2. 规则引擎使用问题:使用规则引擎并非总能达预期,部分产品不佳,有些缺少衔接两种实现环境模型概念相关性的无缝视图,易致应用程序割裂。 3.使用规则时的关键要点:使用规则要兼顾模型,团队需找到适配两种实现范式的单一模型,前提是规则引擎支持富有表达力的实现方式。若无无缝环境,开发人员需提炼清晰基本概念组成的模型。 4. 融合工具与语言:健壮的 UBIQUITOUS LANGUAGE 是将各部分紧密结合的有效工具,坚持在两种环境使用一致名称并用其讨论,可消除环境间鸿沟。 5. MODEL - DRIVEN DESIGN 与实现:MODEL - DRIVEN DESIGN 不一定面向对象,但需富有表达力的模型结构实现,若工具缺乏表达力应重新考虑。 6. 混合非对象元素的经验规则: - 6.1 不与实现范式对抗,寻找适配范式的模型概念。 - 6.2 依靠通用语言,保持语言使用一致性防止设计分裂。 - 6.3 不依赖 UML,依情况选择合适表达方式。 - 6.4 保持怀疑,确定工具真正有用再使用,使用混合范式前先尝试主要范式的各种可能。 7. 关系范式特例:关系范式是范式混合特例,关系数据库作为常用非对象技术与对象模型关系紧密,第 6 章将讨论其存储对象数据及相关挑战。 ## 6. 领域对象的生命周期 总结要点: 1. 对象生命周期阶段:创建→状态变化→最终消亡(存档/删除) 2. 两类对象管理差异: - 临时对象:简单构造/计算/自动回收(无需复杂管理) - 复杂对象: • 长生命周期且部分时间不在活动内存 • 与其他对象存在复杂依赖关系 • 状态变化需遵循固定规则 • 管理不当易导致模型驱动设计偏离 3. 核心挑战:如何在复杂依赖关系中有效管理状态变化规则,确保设计一致性 --- 主要挑战: 1. 核心挑战: - 生命周期全程维护模型完整性 - 防止模型被生命周期管理复杂性拖累 2. 三大解决方案模式: (1) AGGREGATE(聚合) • 定义清晰归属关系与边界 • 避免复杂对象关系网 • 确保生命周期各阶段完整性维护 (2) FACTORY(工厂) • 负责创建/重建复杂对象及聚合 • 封装内部结构细节 (3) REPOSITORY(存储库) • 处理持久化对象的查找/检索 • 封装基础设施复杂性 • 虽非领域原生但在领域设计中关键 3. 协同作用: - AGGREGATE作为基础划分模型范围 - FACTORY/REPOSITORY在聚合基础上操作 - 三者结合实现: • 模型对象全生命周期系统性管理 • 特定生命周期转换复杂性封装 • 增强模型驱动设计的完整性与可操作性 ### 6.1 模式: aggregate 例子: ```cpp #include #include #include // 领域模型 - 聚合根 class PurchaseOrder { public: // 根实体唯一标识 struct Id { explicit Id(int value) : value_(value) {} int value() const { return value_; } private: int value_; }; // 值对象 - 采购项 class LineItem { public: LineItem(std::string product, double price, int quantity) : product_(std::move(product)), price_(price), quantity_(quantity) {} double amount() const { return price_ * quantity_; } // 值对象不可变性 const std::string& product() const { return product_; } double price() const { return price_; } int quantity() const { return quantity_; } private: std::string product_; double price_; int quantity_; }; // 构造函数(工厂方法) static PurchaseOrder create(Id id, double maxAmount) { return PurchaseOrder(id, maxAmount); } // 聚合根方法 - 添加采购项 void addLineItem(LineItem item) { // 验证固定规则:总额不能超过maxAmount if (totalAmount() + item.amount() > maxAmount_) { throw std::runtime_error("超过订单金额限制"); } lineItems_.push_back(item); } // 聚合根方法 - 获取总额 double totalAmount() const { double total = 0; for (const auto& item : lineItems_) { total += item.amount(); } return total; } // 根实体标识 Id id() const { return id_; } private: // 聚合内部状态 PurchaseOrder(Id id, double maxAmount) : id_(id), maxAmount_(maxAmount) {} Id id_; double maxAmount_; std::vector lineItems_; }; // 使用示例 int main() { try { // 创建聚合根 auto order = PurchaseOrder::create(PurchaseOrder::Id(123), 1000.0); // 添加采购项(内部验证规则) order.addLineItem(PurchaseOrder::LineItem("笔记本电脑", 899.99, 1)); order.addLineItem(PurchaseOrder::LineItem("鼠标", 49.99, 2)); // 输出总额 std::cout << "订单总额: " << order.totalAmount() << std::endl; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl; } } ``` 豆包总结要点: 1. 核心问题: - 复杂对象关系导致生命周期管理困难(关联爆炸、依赖混乱) - 数据库事务中难以维护数据一致性规则 - 并发访问下的对象锁定冲突与性能问题 2. AGGREGATE模式核心设计: (1) 结构定义: • 根(Root):全局唯一标识的ENTITY • 边界(Boundary):定义聚合内部对象范围 • 内部对象:本地标识的ENTITY或VALUE OBJECT (2) 实施规则: ▶ 外部仅可引用根ENTITY ▶ 内部对象间可自由引用 ▶ 级联删除(删除根即清理所有内部对象) ▶ 事务提交时验证内部固定规则 ▶ 根负责规则验证与状态管理 3. 关键优势: - 明确模型边界,减少对象关系复杂度 - 确保事务一致性(内部规则实时验证) - 简化并发控制(基于根对象锁定) - 提升模型可理解性与设计沟通效率 4. 典型应用场景: - 采购订单系统示例: • PO作为根聚合,包含采购项 • 价格信息本地化存储于采购项 • 解决并发编辑冲突与总额校验问题 5. 协同模式关系: - AGGREGATE作为基础模型单元 - FACTORY负责聚合创建(封装复杂构造逻辑) - REPOSITORY管理聚合持久化(封装存储细节) - 三者结合实现生命周期全阶段管理 6. 实现注意事项: - 需领域专家参与划分聚合边界 - 技术框架支持(如自动锁机制)可提升实施效率 - 跨聚合规则通过异步处理(事件/批处理)实现最终一致 ### 6.2 模式: factory ```cpp #include #include #include // 图书聚合根 class Book { public: // 图书的唯一标识符 struct Id { explicit Id(int value) : value_(value) {} int value() const { return value_; } private: int value_; }; // 构造函数设为私有,外部不能直接创建 Book(Id id, const std::string& title, const std::string& author) : id_(id), title_(title), author_(author) {} // 获取图书的 ID Id id() const { return id_; } // 获取图书的标题 std::string title() const { return title_; } // 获取图书的作者 std::string author() const { return author_; } private: Id id_; std::string title_; std::string author_; }; // 图书工厂类 class BookFactory { public: // 工厂方法,用于创建图书 static Book createBook(int idValue, const std::string& title, const std::string& author) { // 可以在这里添加更复杂的验证逻辑 if (title.empty()) { throw std::invalid_argument("图书标题不能为空"); } if (author.empty()) { throw std::invalid_argument("图书作者不能为空"); } Book::Id id(idValue); return Book(id, title, author); } }; int main() { try { // 使用工厂创建图书 Book book = BookFactory::createBook(1, "C++ Primer", "Stanley Lippman"); std::cout << "图书 ID: " << book.id().value() << std::endl; std::cout << "图书标题: " << book.title() << std::endl; std::cout << "图书作者: " << book.author() << std::endl; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl; } return 0; } ``` 创建对象或aggregate流程很复杂或暴露了过多内部结构时,应该用 factory 封装。 对象的功能主要体现在`复杂的内部配置及关联`, 应提炼去除与对象意义或交互中的角色无关的内容。让复杂对象承担自身创建会导致职责过载。 例如:汽车的装配和驾驶永远不会同时发生,所以合并两种功能是没价值的。 但将创建职责交给另一个相关方(client)可能会导致暴露对象的内部结构与对象的一些规则,甚至调用构造函数会与具体类产生耦合。 客户创建对象会牵涉不必要的复杂性,并把职责搞得模糊不清,违背了 aggregate 的封装要求,这破坏了领域层抽象的优势。 对象创建本身可以是一个主要操作,但被创建的对象不适合承担复杂的装配操作。 复杂对象创建是领域层的职责,这项任务不属于表示模型的对象。对象的创建和装配一般情况没有意义,但是有些情况如"开设银行账户"则属于重大事件。因此我们要在设计中添加一个`新元素` -- factory,它不对应模型中的任何事物,但又确实承担了领域层的部分职责。`它需要一个更加抽象且不与其他对象发生耦合的构造机制`,它负责创建其他对象。 ```plantuml @startuml object client object factory object prodect client -right-> factory : new(parameters),客户指定需要什么 factory --> client : prodect factory -right-> prodect : create(factory负责生成满足客户需求的内部对象) @enduml ``` `具体定义`: factory 封装了创建复杂对象或aggregate所需的知识,提供反映客户目标的接口,以及被创建对象的抽象视图. factory有很多设计模式,包括 factory method, abstract factory, builder. 好的工厂需要满足两个基本需求: 1. 创建方法是原子的, 且要保证被创建对象或aggregate的所有固定规则。factory生成的对象要处于一致的状态。 2. factory 应该抽象为所需的类型,而不是要创建的具体的类。 #### 6.2.1 选择factory及其应用位置 一般来说factory作用是隐藏创建对象的细节, 我们把factory用在需要隐藏细节的地方,这通常与aggregate有关。 1. 向已存在的aggregate添加元素可以在根上创建 factory method ```cpp #include #include // 前置声明 class Order; // 订单商品项类 class OrderItem { public: OrderItem(int orderId, int productId, int quantity) : orderId_(orderId), productId_(productId), quantity_(quantity) {} int getProductId() const { return productId_; } int getQuantity() const { return quantity_; } private: int orderId_; int productId_; int quantity_; }; // 订单类,作为聚合根 class Order { public: Order(int orderId, int customerId) : orderId_(orderId), customerId_(customerId) {} // 在聚合根上的工厂方法,用于创建订单商品项 OrderItem createOrderItem(int productId, int quantity) { OrderItem item(orderId_, productId, quantity); orderItems_.push_back(item); return item; } int getOrderId() const { return orderId_; } private: int orderId_; int customerId_; std::vector orderItems_; }; int main() { Order order(1, 100); OrderItem orderItem = order.createOrderItem(200, 5); std::cout << "订单 ID: " << order.getOrderId() << ", 订单商品项产品 ID: " << orderItem.getProductId() << ", 数量: " << orderItem.getQuantity() << std::endl; return 0; } ``` 2. 在一个对象上使用 factory method, 这个对象与生成的另一个对象密切相关,但并不拥有所生成的对象。 ```cpp #include // 产品类 class Product { public: Product(int productId, double price) : productId_(productId), price_(price) {} int getProductId() const { return productId_; } double getPrice() const { return price_; } private: int productId_; double price_; }; // 订单商品项类 class OrderItem { public: OrderItem(int orderId, const Product& product, int quantity) : orderId_(orderId), productId_(product.getProductId()), price_(product.getPrice()), quantity_(quantity) {} int getProductId() const { return productId_; } double getPrice() const { return price_; } int getQuantity() const { return quantity_; } private: int orderId_; int productId_; double price_; int quantity_; }; // 客户类,与订单商品项创建密切相关,但不拥有订单商品项 class Customer { public: // 在客户类上的工厂方法,用于创建订单商品项 OrderItem createOrderItem(int orderId, const Product& product, int quantity) { return OrderItem(orderId, product, quantity); } }; int main() { Product product(200, 19.99); Customer customer; OrderItem orderItem = customer.createOrderItem(1, product, 5); std::cout << "订单商品项产品 ID: " << orderItem.getProductId() << ", 价格: " << orderItem.getPrice() << ", 数量: " << orderItem.getQuantity() << std::endl; return 0; } ``` #### 6.2.2 有些情况下只需要使用构造函数 有时候factory会使不具有多态性的简单对象复杂化。以下情况最好使用简单公有的构造函数。 1. 类是一种类型, 不是任何相关层次结构的一部分。 ```cpp // 独立的类,不参与任何继承层次结构 class Circle { private: double radius; public: // 构造函数,创建 Circle 对象时直接初始化半径 Circle(double r) : radius(r) {} // 计算圆的面积 double area() const { return 3.14159 * radius * radius; } }; ``` 2. 客户关心的是实现,可能是将其作为 strategy 的一种方式 ```cpp #include #include #include // 策略接口 class SortStrategy { public: virtual void sort(std::vector& data) = 0; virtual ~SortStrategy() {} }; // 具体策略:升序排序 class AscendingSort : public SortStrategy { public: void sort(std::vector& data) override { std::sort(data.begin(), data.end()); } }; // 具体策略:降序排序 class DescendingSort : public SortStrategy { public: void sort(std::vector& data) override { std::sort(data.rbegin(), data.rend()); } }; // 上下文类,使用策略 class Sorter { private: SortStrategy* strategy; public: Sorter(SortStrategy* s) : strategy(s) {} void setStrategy(SortStrategy* s) { strategy = s; } void performSort(std::vector& data) { strategy->sort(data); } }; int main() { std::vector data = {5, 3, 8, 1, 2}; // 客户明确选择升序排序策略 AscendingSort ascending; Sorter sorter(&ascending); sorter.performSort(data); std::cout << "升序排序结果: "; for (int num : data) { std::cout << num << " "; } std::cout << std::endl; // 客户明确选择降序排序策略 DescendingSort descending; sorter.setStrategy(&descending); sorter.performSort(data); std::cout << "降序排序结果: "; for (int num : data) { std::cout << num << " "; } std::cout << std::endl; // 不适合使用工厂模式的原因: // 1. 客户关心具体的策略实现,需要明确指定使用哪种策略,工厂模式会隐藏具体的实现细节,不符合客户的需求。 // 2. 策略模式强调的是客户可以灵活地切换不同的策略,直接使用具体的策略类实例可以更直观地体现这种灵活性。 // 3. 使用工厂模式会增加代码的复杂度,例如需要额外创建工厂类和根据条件选择不同策略的逻辑。 return 0; } ``` 3. 客户可以访问对象的所有属性,向客户公开的构造函数中没有嵌套的对象创建 ```cpp #include // 简单的类,客户可访问所有属性,构造函数无嵌套对象创建 class Person { public: // 公开的属性 std::string name; int age; // 公开的构造函数,没有嵌套的对象创建 Person(const std::string& n, int a) : name(n), age(a) {} }; int main() { // 客户直接使用构造函数创建对象 Person person("Alice", 25); // 客户可以直接访问对象的属性 std::cout << "姓名: " << person.name << ", 年龄: " << person.age << std::endl; // 不适合使用工厂模式的原因: // 1. 构造函数简单直接,没有复杂的初始化逻辑或嵌套对象创建,直接使用构造函数创建对象更加清晰明了。 // 2. 客户可以直接访问对象的所有属性,说明对象的创建和使用逻辑很简单,不需要工厂模式来封装创建过程。 // 3. 使用工厂模式会引入额外的类和方法,增加了代码的复杂度,而没有带来明显的好处。 return 0; } ``` 4. 构造并不复杂 #### 6.2.3 接口的设计 1. 每个操作必须是原子的,所需信息一次传递, 失败行为必须固定 2. factory 将与其他参数耦合, ```cpp #include #include // 前置声明抽象类型 class AbstractPart; // 订单商品项类 class PurchaseOrderItem { private: // 这里使用抽象类型指针 AbstractPart* part; int quantity; double price; public: PurchaseOrderItem(AbstractPart* p, int q, double pr) : part(p), quantity(q), price(pr) { // 可以添加更多的初始化逻辑 } void display() const { std::cout << "Part: [Abstract], Quantity: " << quantity << ", Price: " << price << std::endl; } }; // 抽象部件类 class AbstractPart { public: virtual ~AbstractPart() = default; virtual void info() const = 0; }; // 具体部件类 class ConcretePart : public AbstractPart { public: void info() const override { std::cout << "This is a concrete part." << std::endl; } }; // 订单聚合根类 class PurchaseOrder { private: int orderId; public: PurchaseOrder(int id) : orderId(id) {} // 工厂方法 PurchaseOrderItem createItem(AbstractPart* part, int quantity, double price) { // 原子操作:在一次交互中接收所有必要信息 // 检查固定规则,这里简单示例为数量必须大于 0 if (quantity <= 0) { // 创建失败时抛出异常 throw std::invalid_argument("Quantity must be greater than 0."); } if (price < 0) { throw std::invalid_argument("Price cannot be negative."); } return PurchaseOrderItem(part, quantity, price); } }; int main() { try { PurchaseOrder order(1); ConcretePart part; // 使用抽象类型指针传递参数 PurchaseOrderItem item = order.createItem(&part, 5, 10.0); item.display(); } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } return 0; } ```