第三章:灵活性设计

  为了让项目随着开发的进行而加速 - 而不是被自己的遗产所压垮 - 需要一种乐于与之合作的设计,拥抱改变,进行柔软的设计。

柔性设计是深度建模的补充。

  开发人员扮演两个角色,每个角色都必须有设计职责,同一个人可能会扮演两个角色 - 甚至在几分钟内来回切换 - 但与代码的关系仍然不同。一个角色是客户端的开发人员,他们利用设计的功能将领域对象编织到应用程序代码或其他域层代码中,柔软的设计揭示了深层潜在的模型,使其潜力清晰,客户端开发人员可以灵活地使用一组最小的松散耦合概念来表达域中的一系列场景,设计元素以自然的方式组合在一起,结果具有可预测性,清晰的特征和稳健性。

  同样重要的是,设计必须为致力于改变它的开发人员服务,要变得开放,设计必须易于理解,揭示客户断开发人员正在使用的相同底层模型,它必须遵循领域的深层模型的轮廓,因此大多数更改会在灵活点处进行扭曲设计,其代码的效果必须明显,才能才能很容易预测更改的后果。

•使行为明显

•降低变更成本

•创建软件的开发人员一起合作

意图揭示:接口

  如果开发人员为了使用组件还要参考组件的实现才能使用这个组件,那么封装就毫无意义。如果原开发人员以外的其他人必须根据其实现来推断对象或操作的目的,那么新开发人员可能会推断出设计这些操作或类的目的只是因为偶然实现。如果不使用意图,那么代码可能只暂时起作用,这时设计的基础概念已经被破坏,并且两个开发人员将在交叉目的下工作。

因此:

  命名类和方法来描述它们的效果和目的,而不是参考它们做出承诺的方式。这使客户端开发人员无需理解内部。这些名称应符合无所不在的语言,以便团队成员可以快速推断其含义。在创建行为之前编写测试,以强制您的思维进入客户端开发人员模式。


无副作用的函数

  多个规则或计算组合的相互作用会变得极难预测,调用操作函数的开发人员必须了解其实现及其所有代理的实现,以便预测结果。如果开发人员被迫揭开面纱,那么任何接口抽象的有用性就是有限的。如果没有安全可预测的抽象,开发人员必须限制组合爆炸,对可进行构建的丰富行为组合要设置较低的上限。

因此:

  将尽可能多的程序逻辑放入函数中,返回结果且没有可观察到的副作用的操作。严格地将命令(导致对可观察状态进行修改的方法)分离为无返回领域信息的非常简单的操作。当适合使用职责的概念表达时,就将复杂逻辑移动到值对象中来进一步控制副作用。

值对象的所有操作都应该是无副作用的函数。

断言

  当操作的副作用只是由它们的实现隐含地定义时,具有大量委托的设计变成了因果关系的混乱。理解程序的唯一方法是通过分支路径跟踪执行,封装的价值丢失了,追踪具体执行的必要性战胜了抽象。(banq注:如果你想查看Tomcat源码,说明你认为追踪的必要性高于Tomcat服务器概念的抽象封装)

因此:

  状态需要操作后置条件和类与聚合的不变性,如果断言无法直接在您的编程语言中编码,请为它们编写自动单元测试,将它们写入符合项目开发过程风格的文档或图表中。

  使用一致的概念集寻找模型,这使得开发人员可以推断出预期的断言,加速学习曲线并降低矛盾代码的风险。

断言是一种定义服务和实体修饰符的契约。

断言是定义聚合上的不变量。

#契约设计

独立的类

  即使在模块中,随着依赖性的增加,解释设计的难度也会大大增加,这增加了精神过载,限制了开发人员可以处理的设计复杂性,隐式概念比显式引用更会加大这种设计复杂性。

  低耦合是对象设计的基础,如果可以,一直保持这种路线,消除路线图中的所有其他概念,然后,类将完全独立,可以单独研究和理解,每个这样的自足类都会显着减轻了理解模块的负担。


闭包操作

  最有趣的对象最终做的事情不能仅仅用初心来表达。(banq注:偏离初心)

因此:

  在适合的地方,定义一个返回类型与其入参数的类型相同的函数操作方法。如果实现者会在这个函数方法计算中使用了状态,那么实现者实际上是一个有效的函数的入参,因此函数的入参和返回应该类型相同,这种操作在该类型的实例集合下闭合。闭合操作提供高级接口,而不会对其他概念产生任何依赖性。

  此模式通常应用于值对象的操作,由于实体的生命周期在领域中具有重要意义,因此您不能每次设计一个新实体来回答新问题。有些操作在实体类型下关闭,您可以向Employee实体对象询问其主管并获取另一个Employee实体。但总的来说,实体不是那种可能是计算结果的概念,因此,在大多数情况下,闭包操作是可以在值对象中寻找使用的机会。

  你有时会只是想这种模式的一半,入参数与实现者匹配,但返回类型不同,或者返回类型与调用接收者的类型匹配,但与入参数类型不同。这些操作并未闭合,但它们确实提供了闭合的一些些优势,可以解放你的思维。

(banq注: 函数式方法的实现方式: public VO yourMethod(VO vo) )

声明性设计

  在过程化的软件中没有真正的保证,过程化方法会回避断言,代码可能会产生额外的副作用,而这些副作用并未明确排除。无论模型如何驱动我们的设计,我们仍然最终需要编写程序来产生概念交互的效果,我们花了很多时间编写样板代码,这些代码并没有真正添加任何含义或行为、意图接口和对本章中的其他模式有所帮助,那么就永远不能给传统的面向对象程序提供正式的严谨性。

  这些是声明式设计背后的一些动机,这个术语对很多人来说意味着许多事情,但通常它表示一种编写程序或程序的某些部分作为一种可执行规范的方法,对属性的非常精确的描述实际上控制了软件,在各种形式中,可以通过反射机制或在编译时通过代码生成(基于声明自动生成传统代码)来完成,此方法允许其他开发人员根据其表面形式使用声明,这绝对是一种保证。(banq注:SQL语句也是一种声明设计,比如查询某个数据只要写Select * from table即可,至于如何进行查询等细节由数据库内部实现,这是数据库作为声明设计产品的独特定位)

  如果开发人员有意或无意地绕过它们,许多声明性方法可能会被破坏,当系统难以使用或过度限制时,可能会出现这种情况,每个人都必须遵循框架规则才能获得声明性程序的好处。

一种陈述式的设计风格

  一旦你的设计有意图 - 接口表达、无副作用的函数和断言,你就会进入声明领域。声明性设计的许多好处在这些情况下获得:具有可以传达其含义,具有特征效果或明显效果,或根本没有可观察到的副作用。

  灵活的设计可以使客户端代码使用声明式设计(banq注:将调用代码变成类似只要写SQL那样具有声明性,其实就是要求你的后端业务程序写得像数据库提供SQL那样有明显意图的接口表达)。为了说明,下一节将汇总本章中的一些模式,使规范更加灵活和声明。

借鉴既定的形式框架

  从头开始创建一个紧密的概念框架是你每天都做不到的事情。有时您会在项目的整个生命过程中发现并完善其中的一个(banq注:不断迭代)。但是,您可以经常使用和调整在您的领域或其他领域长期建立的概念系统,其中一些是已经过几个世纪的精炼和提炼,例如,许多商业应用程序涉及会计,会计定义了一套完善的实体和规则,可以轻松适应深层模型和柔软设计。

  有许多这样的形式化概念框架,但我个人最喜欢的是数学(banq注:形式逻辑有数学 计算机语言和法律语言几种语言形式)。挖掘出算法,它们具有天然聚合!

#形式逻辑  

概念轮廓

  有时人们会将功能切割得很好,以便灵活组合 有时他们会把它大块化以封装复杂性;有时他们寻求一致的粒度,使所有类和操作达到类似的规模。这些过于简单化不如一般规则,但他们受到基本问题的驱使。

  当模型或设计的元素嵌入单体构造中时,它们的功能会重复,外部接口并没有说明客户端可能关心的一切,它们的意义很难理解,因为不同的概念混合在一起。

  相反,分解类和方法可能会使客户端无意义地复杂化,迫使客户端理解如何将小块组合在一起(banq注:细化以后使用的时候就需要拼凑集成在一起)。更糟糕的是,细化分析一个概念可能完全丢失原来的概念,铀原子的一半就不是铀了。当然,重要的不仅仅是颗粒大小,而是粒度所在运行的地方。

因此:

  将设计元素(操作,接口,类和聚合)作为分解的最小内聚单元,同时考虑到您对域中重要划分的直觉,通过连续的重构观察变化维度和稳定性,并寻找解释这些剪切模式的基本概念轮廓,将模型与领域的一致方面对齐,使其成为可行的知识领域。

  基于深度模型的柔软设计产生了一组简单的接口,这些接口在逻辑上结合起来,在无处不在的语言中做出合理的陈述,并且没有不相关选项的分心和维护负担。(banq注:参考微服务)

第四章:战略设计之上下文映射

领域驱动设计参考

函数式编程