[译文]Domain|[译文]Domain Driven Design Reference(四)—— 柔性设计
如果这是第二次看到我的文章,欢迎订阅z哥的公众号(跨界架构师)哦~
每周五11:45 按时送达。当然了,也会时不时加个餐~
本书是Eric Evans对他自己写的《领域驱动设计-软件核心复杂性应对之道》的一本字典式的参考书,可用于快速查找《领域驱动设计》中的诸多概念及其简明解释。
其它本系列其它文章地址:
[译文]Domain Driven Design Reference(一)—— 前言
[译文]Domain Driven Design Reference(二)—— 让模型起作用
[译文]Domain Driven Design Reference(三)—— 模型驱动设计的构建模块
[译文]Domain Driven Design Reference(四)—— 柔性设计
Ⅲ.柔性设计 要让一个项目加速发展,而不是被其自身的历史包袱所拖累,需要一个能与之良好协作的设计,它会带来改变。一个柔性设计。
柔性设计是对深度建模的补充。
开发人员扮演两种角色,每种角色都必须通过设计来完成。同一个人可能扮演这两个角色,甚至可以在几分钟内来回切换,但与代码的关系却不是这样。一个角色是客户端的开发人员,他们利用设计的方式将领域对象编织到应用程序代码或其他域层代码中。柔性设计揭示了一个深层次的潜在模型,使其潜在性变得清晰。客户端开发人员可以灵活地使用一组最小松散耦合的概念来表示域中的一系列场景。设计元素以自然的方式融合在一起,其结果是可预测的,清晰的特征并且是健壮的。
同样重要的是,设计必须服从于开发人员来改变它。要做出改变,设计必须易于理解,并能表达出客户端开发人员正在使用的相同底层模型。它必须遵循该领域深层模型的概念,所以大多数变化都会在灵活点上柔性设计。其代码的影响必须透明明显,因此更改的后果将很容易预测。
?使行为特征明显
?降低变更成本
?创建与之合作的软件开发人员
释意接口 如果开发人员必须考虑组件的实现才能使用它,则封装的价值就没有了。如果原始开发人员以外的人必须根据其实现来推断对象或操作的目的,新的开发者可能会推断出一个意图——操作或类只是偶然地执行。如果这不是意图的话,那么代码可能暂时有效,但设计的概念基础已经被破坏了,两个开发人员将在交叉目的下工作。
因此:
命名类名和操作名来描述它们的效果和目的,而不用参考它们做出履约的方法。这减轻了客户开发者理解内部的必要性。这些名字应符合通用语言,以便团队成员可以快速推断其含义。在创建它之前为行为编写一个测试,以强制您的思维进入客户端开发人员模式。
无副作用方法 多个规则或计算的组合的相互作用变得非常难以预测。开发人员调用一个操作必须理解它的实现以及所有委托的实现,以便预测结果。如果开发人员被迫刺破遮罩层,任何抽象接口的用处都是有限的。如果没有安全可预测的抽象,开发人员必须限制组合爆炸,对可行的丰富行为设置较低的上限。
因此:
将尽可能多的程序逻辑放入函数中,返回没有明显副作用的结果。严格地将命令(引起明显的状态改变的方法)分隔成不返回领域信息的非常简单的操作。当发现了一个符合职责的概念时,通过将复杂的逻辑转化为值对象来进一步控制副作用。
值对象的所有操作都应该是无副作用的函数。
断言 当操作的副作用只是通过实现而隐含地定义时,大量委托的设计就会成为一种混乱的因果关系。理解程序的唯一方法是通过分支路径来跟踪执行。封装的价值失去了。跟踪具体执行的必要性使抽象也失败了。
因此:
状态操作的后置条件以及类和聚合的不变性。如果断言不能直接用你的编程语言编写,请为它们编写自动单元测试。将它们写入符合项目开发过程风格的文档或图表中。
寻找具有相关概念集的模型,这些概念引导开发人员推断预期的断言,加速学习曲线并降低矛盾代码的风险。
断言定义了服务和实体修饰符的契约。
断言在聚合上定义了不变性。
孤立类 即使在同一个模块中,随着依赖性的增加,解释设计的难度也会大幅增加。这增加了理解的负担,限制了开发人员可以处理的设计复杂性。隐式概念对此负担的促进作用甚至超过了显示的引用。
低耦合是面向对象设计的基础。如果可以,一直这样做。消除图中的所有其他概念。然后类将完全独立,可以单独研究和理解。每个这样的自包含类都显着减轻了理解一个模块的负担。
闭合操作 大多数有趣的对象最终都只能做一些无法用基本元素来表示的东西。
因此:
【[译文]Domain|[译文]Domain Driven Design Reference(四)—— 柔性设计】在适当的情况下,在定义操作时让它返回类型与其参数的类型相同。如果实现者的状态在计算中会被用到,那么实现者实际上就是操作的一个参数,因此参数和返回值应该与实现者有相同的类型。这种操作就是在该类型的实例集合中的闭合操作。闭合操作提供了一个高层接口,而不会引入对其他概念的依赖。
这种模式通常应用于值对象的操作。因为一个实体的生命周期在领域中具有重要意义,所以你不能创造一个新的实体来回答一个问题。也有一些操作在实体类型下闭合。可以向其主题对象请求一个属性对象并取回另一个属性。但总的来说,实体并不是那种适合成为计算结果的概念。所以,大多数情况下,这是一个寻找值对象的机会。
你有时会陷入这种模式的一半。参数与实现者匹配,但返回类型不同,或者返回类型与接收者匹配,参数不同。这些操作并不是闭合的,但是他们给与了思考闭合的一些优势的想象空间。
声明式设计 在程序软件中不可能有真正的保证。仅以一种逃避断言的方式命名,代码可能会产生额外的副作用而这些副作用并没有被排除在外。无论我们的设计如何以模型为导向,我们仍然最终编写程序来产生概念上的交互效果。并且我们花费大部分时间在编写样板代码上,而这些代码并没有实际增加任何意义或者行为。本章中的释意接口和其它模式有所帮助,但是它们不可能给传统的面向对象提供形式上的严谨。
这些是声明式设计背后的动机。这个术语对很多人来说意味着许多东西,但通常它指示一种编写一个程序或者程序的某个部分的方式,作为一种可执行的规范。对属性的非常精确的描述实际控制着软件。在它的各种形式中,可以通过反射机制或编译时通过代码生成来完成(基于声明自动生成传统代码)。这种方法允许其他开发人员以表面价值接受声明。这是一个绝对的保证。
如果开发人员有意或无意地绕过它们,很多声明式方法可能会被破坏。当系统难以使用或限制过多时,这很可能发生。每个人都必须遵守框架的规则才能获得声明式编程的好处。
一种声明式的设计风格 一旦你的设计有释意接口,无副作用函数和断言,你就会进入声明式领域。声明式设计的许多好处都是在您具有可交流其含义的可组合元素,并且具有特征或明显效果,或根本没有可观察效果时获得的。
柔性设计可以使客户端代码使用声明式的设计风格成为可能。为了说明这一点,下一节将介绍本章中的一些模式,以使规范更加灵活和声明性。
图上的形式主义(形式化的绘画) 从零开始创建一个严格的概念框架是你每天都不能做的事情。有时在项目的生命周期中,您会发现并改进其中的一个。但是你可以经常使用和调整那些长期在你的领域或其他领域建立起来的概念系统,其中一些已经经过了几个世纪的精炼和提炼。例如,许多商业应用程序都涉及会计。会计定义了一套完善的实体和规则,可以轻松适应深层模型和柔性设计。
有许多这样的形式化的概念框架,但我个人最喜欢的是数学。让人惊讶的是,在基本算法上做一些改变是多么有用。很多领域包括数学。寻找它。挖出来。专业的数学是干净的,可以通过清晰的规则组合起来,并且人们发现它很容易理解。
在本书的第8章讨论一个真实世界的例子“Shares Math”,领域驱动设计。
概念轮廓 有时人们会为了灵活的组合而砍掉一些功能。有时候他们会把它封装得很复杂。有时他们会寻求一致的粒度,使所有类别和操作达到相似的程度。这些都是过于简单化的,不像一般规则那样有效。但是他们通过基本问题来激发。
当模型或设计的元素被嵌入到一个整体结构中时,它们的功能会被复制。外部接口并不表示客户端可能关心的所有内容。他们的意思很难理解,因为不同的概念是混合在一起的。
相反,分解类和方法可能会使客户端无意识去复杂化,迫使客户端对象了解如何将小块组合在一起。更糟糕的是,一个概念可能完全丧失。铀原子的一半不是铀。当然,重要的不是颗粒度的小大,但这只是颗粒度的来源。
因此:
将设计元素(操作、接口、类和聚合)分解为内聚单元,同时考虑到您对领域重要分支的直觉。通过连续的重构观察核心的变化和稳定性,并寻找解释这些切分方式的基本概念轮廓。对比模型与领域一致的方面,首先让它成为一个可行的知识领域。
基于深度模型的柔性设计产生了一组简单的接口,这些接口逻辑上可以在通用语言中作出合理的声明,并且没有无关选项的干扰和维护负担。
?作者:Zachary(个人微信号:Zachary-ZF)定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些深度思考。
微信公众号(首发):跨界架构师。<-- 点击查阅近期热门文章
如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。
如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。
扫码加入小圈子
文章图片
推荐阅读
- 《山魈》译文
- 《诫己书》译文
- 《山海经》原文译文——《东山经》之一
- 译文|借助|译文|借助 Pulsar Functions 迁移到无服务应用程序
- 译文 | 敏捷真的是开发者的绊脚石吗()
- An error was encountered while running (Domain = NSPOSIXErrorDomain, Code = 3)解决方法
- An error was encountered while running (Domain = NSPOSIXErrorDomain, Code = 3)
- NodeMCU文档中文翻译|NodeMCU文档中文翻译 5 上传代码
- NodeMCU文档中文翻译|NodeMCU文档中文翻译 2 首页
- NodeMCU文档中文翻译|NodeMCU文档中文翻译 1 概要