领域驱动设计-软件核心复杂应对之道:第十章

10.柔性设计

软件的最终目的是为用户服务。但首先它必须为开发人员服务。在强调重构的软件开发过程中尤其如此。随着程序的演变,开发人员将重新安排并重写每个部分。他们会把原有的领域对象集成到应用程序中,也会让它们与新的领域对象进行集成。甚至几年以后,维护程序还将修改和扩充代码。人们必须要做这些工作,但他们是否愿意呢?

当具有复杂行为的软件缺乏一个良好的设计时,重构或元素的组合会变得很困难。一旦开发人员不能十分肯定地预知计算的全部含义,就会出现重复。当设计元素都是整块的而无法重新组合的时候,重复就是一种必然的结果。我们可以对类和方法进行分解,这样可以更好地重用它们,但这些小部分的行为又变得很难跟踪。如果软件没有一个条理分明的设计,那么开发人员不仅不愿意仔细地分析代码,而且修改代码也可能会产生问题-要么加重了代码的混乱状态,要么由于某种未预料到的依赖性而破坏了某种结构。在任何一种系统中(除非是一些非常小的系统),这种不稳定性使我们很难开发出丰富的功能,而且限制了重构和迭代式的精化。

为了使项目能够随着开发工作的进行加速前进,而不会由于它自己的老化停滞不前,设计必须要让人们乐于使用,而且易于做出修改。这就是柔性设计(supple design)

10.1 intention-revealing interfaces(意图提示接口)

  1. intention-revealing interfaces(意图提示接口):类和方法(包括接口)从名字上要描述它们的效果和目的,而不是表示它们是通过何种方式达到目的的,如果一个开发人员为了使用一个类或一个方法必须研究它的实现,那封装就失去了价值,进而设计的概念基础已经被误用了,所以可能被误用

10.2 side-effect-free function(无副作用函数)

  1. side-effect-free function(无副作用函数)
    1. 修改会产生副作用(改变状态)
    2. 返回结果而不产生副作用的操作叫做函数
    3. 将修改和函数分开。确保导致状态改变的方法不返回领域数据,并尽可能保持简单
    4. 实在有复杂的逻辑,主要针对可能产生副作用的,尽量放到value object

10.3 assertion(断言)

  1. assertion(断言)
    1. 在不了解内部实现需要知道操作的结果,即一个方法知道执行后的结果或状态是什么样的是确定的
    2. 把操作的后置条件及aggregate的固定规则描述清楚
    3. 寻找概念上内聚的模型,以便方便开发人员推断预期的assertion

10.4 conceptual contour(概念轮廓)

  1. conceptual contour(概念轮廓)
    1. 通过反复重构最终会实现柔性设计,以上就是其中的一个原因。随着代码不断适合新理解的概念或需求,概念轮廓就逐渐形成了。
    2. 把设计元素(操作、接口、类和aggregate)分解为内聚的单元,在这个过程中,你对领域中一切重要划分的直观认识也要考虑在内。在连续的重构过程中观察发生变化和保证稳定的规律性,并寻找能够解释这些变化模式的底层概念轮廓。使模型与领域中那些一致的方面(正是这些方面使得领域成为一个有用的知识体系)相匹配
    3. 设计即使是按照概念轮廓进行,也仍然需要修改和重构。当连续的重构往往只是一些局部修改(而不是对模型的概念产生大范围的影响)时,这就是模型已经与领域相吻合的信号。如果遇到了一个需求,它要求我们必须大幅度地修改对象和方法的划分,那么这就是在向我们传递这样一条信息:我们对领域的理解还需要精化。它提供了一个深化模型并且使设计变得更加具有柔性的机会。
    4. 概念过载(conceptual overload)-当模型中的互相依赖性过多时,我们就必须把大量问题放在一起考虑

10.5 standalone class(孤立的类)

  1. standalone class(孤立的类)
    1. 低耦合是对象设计的一个基本要素,尽一切可能保持低耦合。把其他所有无关概念提取到对象之外。这样类就变成完全孤立的了,这就使得我们可以单独地研究和理解它。每个这样的孤立类都极大地减轻了因理解module而带来的负担。
    2. 当一个类与它所在的模块中的其他类存在依赖关系时,比它与模块外部的类有依赖关系要好得多。同样,当两个对象具有自然地紧密耦合关系时,这两个对象共同涉及的多个操作实际上能够把它们的关系本质明确地表示出来。我们的目标不是消除所有依赖,而是消除所有不重要的依赖。当无法消除所有的依赖关系时,每消除一个依赖对开发人员而言都是一种解脱,使他们能够集中精力处理剩下的概念依赖关系。
    3. 尽力把最复杂的计算提取到standalone class(孤立的类)中,可能实现此目的的一种方法是把具有紧密联系的类中所含有的value object建模出来。
    4. 低耦合是减少概念过载的最基本方法。孤立的类是低耦合的极致。
    5. 消除依赖性并不是说要武断地把模型中的一切都简化为基本类型,这样只会削弱模型的表达能力。

10.6 clousure of operation(闭合操作)

  1. clousure of operation(闭合操作):在减小依赖性的同时保持丰富接口的技术
    1. 大部分引起我们兴趣的对象所产生的行为仅用基本类型是无法描述的。
    2. 在适当的情况下,在定义操作时让它的返回类型与其参数的类型相同。如果实现者(implementer)的状态在计算中会被用到,那么实现者实际上就是操作的一个参数,因此参数和返回值应该与实现者有相同的类型。这样的操作就是在该类型的实例集合中的闭合操作。闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖性。
    3. 一个操作可能是在某一抽象类型之下的闭合操作,在这种情况下,具体的参数可能有不同的具体类型。例如,加法是实数之下的闭合运算,而实数既可以是有理数,也可以是无理数。
    4. 在尝试和寻找减少互相依赖性并提高内聚性的过程中,有时我们会遇到"半个闭合操作"这种情况。参数类型与实现者的类型一致,但返回类型不同;或者返回类型与接收者的类型相同但参数类型不同。这些操作都不是闭合操作,但它们确实具有闭合操作的某些优点。当没有形成闭合操作的那个多出来的类型是基本类型或基础库类时,它几乎与闭合操作一样减轻了我们的思考负担。
    5. 在Java中,从集合中选择一个元素子集,需要使用迭代器,用迭代器来测试每个元素,把匹配的元素收集到一个新的collection中。是否真的有必要使用Iterator这个额外的概念以及它所带来的所有机制上的复杂性呢?

10.7 声明式设计

把程序或程序的一部分写成一种可执行的规格(specification)。使用声明式设计时,软件实际上是一些非常精确的属性描述来控制的。声明式设计有多种实现方式,例如,可以通过反射机制来实现,或在编译时通过代码生成来实现。这种方法使其他开发人员能够根据字面意义来使用声明。它是一种绝对的保证。

缺陷:

  • 声明式语言并不足以表达一切所需的东西,它把软件束缚在一个由自动部分构成的框架之内,使软件很难扩展到这个框架之外。
  • 代码生成技术破坏了迭代循环,它把生成的代码合并到手写的代码中,使得重新生成的破坏作用变得很大

声明式设计的最大价值是用一个范围非常窄的框架来自动处理设计中某个特别单调且易出错的方面,比如持久化和对象关系映射。最好的声明式设计能够使开发人员不必去做那些单调乏味的工作,同时又完全不限制他们的设计自由。

特定于领域的语言

10.8 声明式设计风格

用声明式的风格来扩展specification

使用逻辑运算对specification进行组合 AND OR NOT 包含 蕴含

10.9 切入问题的角度

1.分割子领域

2.尽可能利用已有的形式

把命令和无副作用函数分开

把隐式概念变为显式概念

posted @ 2023-06-09 21:36  LHX2018  阅读(20)  评论(0编辑  收藏  举报