《领域驱动设计-软件核心复杂性应对之道》阅读笔记(三)

第三部分 通过重构来加深理解

第8章 突破

重构的投入与回报并非呈线性关系。通常,小的调整会带来小的回报,小的改进也会积少成多。小改进可防止系统退化,成为避免模型变得陈腐的第一道防线。但是,有些最重要的理解也会突然出现,给整个项目带来巨大的冲击。 可以确定的是,项目团队会积累、消化知识,并将其转化成模型。微小的重构可能每次只涉
及一个对象,在这里加上一个关联,在那里转移一项职责。然而,一系列微小的重构会逐渐汇聚成深层模型。
一般来说,持续重构让事物逐步变得有序。代码和模型的每一次精化都让开发人员有了更加清晰的认识。这使得理解上的突破成为可能。之后,一系列快速的改变得到了更符合用户需要并更加切合实际的模型。其功能性及说明性急速增强,而复杂性却随之消失。

8.1 一个关于突破的故事

略~

8.2 机遇

当突破带来更深层的模型时,通常会令人感到不安。与大部分重构相比,这种变化的回报更多,风险也更高。而且突破出现的时机可能很不合时宜。
尽管我们希望进展顺利,但往往事与愿违。过渡到真正的深层模型需要从根本上调整思路, 并且对设计做大幅修改。在很多项目中,建模和设计工作最重要的进展都来自于突破。

8.3 关注根本

不要试图去制造突破,那只会使项目陷入困境。通常,只有在实现了许多适度的重构后才有可能出现突破。在大部分时间里,我们都在进行微小的改进,而在这种连续的改进中模型深层含义也会逐渐显现。
要为突破做好准备,应专注于知识消化过程,同时也要逐渐建立健壮的UBIQUITOUS LANGUAGE。寻找那些重要的领域概念,并在模型中清晰地表达出来(参见第9章)。精化模型, 使其更具柔性(参见第10章)。提炼模型(参见第15章)。利用这些更容易掌握的手段使模型变得 更清晰,这通常会带来突破。
不要犹豫着不去做小的改进,这些改进即使脱离不开常规的概念框架,也可以逐渐加深我们对模型理解。不要因为好高骛远而使项目陷入困境。只要随时注意可能出现的机会就够了。

8.4 后记:越来越多的新理解

略~

第9章 将隐式概念转变为显式概念

9.1 概念挖掘

开发人员必须能够敏锐地捕捉到隐含概念的蛛丝马迹,但有时他们必须主动寻找线索。要挖掘出大部分的隐含概念,需要开发人员去倾听团队语言、仔细检查设计中的不足之处以及与专家观点相矛盾的地方、研究领域相关文献并且进行大量的实验。

9.1.1 倾听语言

倾听领域专家使用的语言。有没有一些术语能够简洁地表达出复杂的概念?他们有没有纠正过你的用词(也许是很委婉的提醒)?当你使用某个特定词语时,他们脸上是否已经不再流露出迷惑的表情?这些都暗示了某个概念也许可以改进模型。
这不同于原来的“名词即对象”概念。听到新单词只是个开头,然后我们还要进行对话、消化知识,这样才能挖掘出清晰实用的概念。如果用户或领域专家使用了设计中没有的词汇,这就是个警告信号。而当开发人员和领域专家都在使用设计中没有的词汇时,那就是一个倍加严重的警告信号了。
或者,应该把这种警告看成一次机会。UBIQUITOUS LANGUAGE是由遍布于对话、文档、模型图甚至代码中的词汇构成的。如果出现了设计中没有的术语,就可以把它添加到通用语言中,这样也就有机会改进模型和设计了。

9.1.2 检查不足之处

你所需要的概念并不总是浮在表面上,也绝不仅仅是通过对话和文档就能让它显现出来。有些概念可能需要你自己去挖掘和创造。要挖掘的地方就是设计中最不足的地方,也就是操作复杂且难于解释的地方。每当有新的需求时,似乎都会让这个地方变得更加复杂。
有时,你很难意识到模型中丢失了什么概念。也许你的对象能够实现所有的功能,但是有些职责的实现却很笨拙。而有时,你虽然能够意识到模型中丢失了某些东西,但是却无法找到解决方案。 这个时候,你必须积极地让领域专家参与到讨论中来。如果你足够幸运,这些专家可能会愿意一起思考各种想法,并通过模型来进行验证。如果你没那么幸运,你和你的同事就不得不自己 思索出不同的想法,让领域专家对这些想法进行判断,并注意观察专家的表情是认同还是反对。

9.1.3 思考矛盾之处

由于经验和需求的不同,不同的领域专家对同样的事情会有不同的看法。即使是同一个人提供的信息,仔细分析后也会发现逻辑上不一致的地方。在挖掘程序需求的时候,我们会不断遇到这种令人烦恼的矛盾,但它们也为深层模型的实现提供了重要线索。有些矛盾只是术语说法上的不一致,有些则是由于误解而产生的。但还有一种情况是专家们会给出相互矛盾的两种说法。
要解决所有矛盾是不太现实的,甚至是不需要的。然而,即使不去解决矛盾,我们也应该仔细思考对立的两种看法是如何同时应用于 同一个外部现实的,这会给我们带来启示。

9.1.4 查阅书籍

在寻找模型概念时,不要忽略一些显而易见的资源。在很多领域中,你都可以找到解释基本概念和传统思想的书籍。你依然需要与领域专家合作,提炼与你的问题相关的那部分知识,然后将其转化为适用于面向对象软件的概念。但是,查阅书籍也许能够使你一开始就形成一致且深层 的认识。
开发人员还有另一个选择,就是阅读在此领域中有过开发经验的软件专业人员编写的资料。阅读书籍并不能提供现成的解决方案,但可以为她提供一 些全新的实验起点,以及在这个领域中探索过的人总结出来的经验。这样可以避免开发人员重复
设计已有的概念。

9.1.5 尝试,再尝试

我们其实别无选择。只有不断尝试才能了解什么有效什么无效。企图避免设计上的失误将会导致开发出来的产品质量低劣,因为没有更多的经验可用来借鉴,同时也会比进行一系列快速实验更加费时。

9.2 如何为那些不太明显的概念建模

面向对象范式会引导我们去寻找和创造特定类型的概念。所有事物及其操作行为是大部分对象模型的主要部分。它们就是面向对象设计入门书籍所讲到的“名词和动词”。但是,其他重要类别的概念也可以在模型中显式地表现出来。

9.2.1 显式的约束

约束是模型概念中非常重要的类别。它们通常是隐含的,将它们显式地表现出来可以极大地 提高设计质量。
有时,约束很自然地存在于对象或方法中。将约束条件提取到其自己的方法中,这样就可以通过方法名来表达约束的含义,从而在设计中显式地表现出这条约束。现在这个约束条件就是一个“有名有姓”的概念了,我们可以用它的名字来讨论它。这种方式也为约束的扩展提供了空间。比这更复杂的规则很容易就会产生比其调用者更长的方法。这样,调用者就可以简单一些,并且只专注于处理自己的任务,而约束条件则可以根据需要进行扩展。
这种独立方法为约束预留了一定的增加空间,但是在很多时候,约束条件是无法用单独的方法来轻松表达的。或者,即使方法自身能够保持其简单性,但它可能会调用一些信息,但对于对象的主要职责而言,这些信息毫无用处。这种规则可能就不适合放到现有对象中。
下面是一些警告信号,表明约束的存在正在扰乱其“宿主对象”的设计:

  • 计算约束所需的数据从定义上看并不属于这个对象。
  • 相关规则在多个对象中出现,造成了代码重复或导致不属于同一族的对象之间产生了继承关系。
  • 很多设计和需求讨论是围绕这些约束进行的,而在代码实现中,它们却隐藏在过程代码中。

如果约束的存在掩盖了对象的基本职责,或者如果约束在领域中非常突出但在模型中却不明显,那么就可以将其提取到一个显式的对象中,甚至可以把它建模为一个对象和关系的集合。

9.2.2 将过程建模为领域对象

首先要说明的是,我们都不希望过程变成模型的主要部分。对象是用来封装过程的,这样我们只需考虑对象的业务目的或意图就可以了。
如果过程的执行有多种方式,那么我们也可以用另一种方法来处理它,那就是将算法本身或其中的关键部分放到一个单独的对象中。这样,选择不同的过程就变成了选择不同的对象,每个对象都表示一种不同的STRATEGY。
过程是应该被显式表达出来,还是应该被隐藏起来呢?区分的方法很简单:它是经常被领域专家提起呢,还是仅仅被当作计算机程序机制的一部分?

9.2.3 模式:SPECIFICATION

“规格”提供了用于表达特定类型的 规则的精确方式,它把这些规则从条件逻辑中提取出来,并在模型中把它们显式地表示出来。
业务规则通常不适合作为ENTITY或VALUE OBJECT的职责,而且规则的变化和组合也会掩盖领域对象的基本含义。但是将规则移出领域层的结果会更糟糕,因为这样一来,领域代码就不再表达模型了。
逻辑编程提供了一种概念,即“谓词”这种可分离、可组合的规则对象,但是要把这种概念用对象完全实现是很麻烦的。同时,这种概念过于通用,在表达设计意图方面,它的针对性不如专门的设计那么好。
幸运的是,我们并不真正需要完全实现逻辑编程即可从中受益。大部分规则可以归类为几种特定的情况。我们可以借用谓词概念来创建可计算出布尔值的特殊对象。那些难于控制的测试方法可以巧妙地扩展出自己的对象。它们都是些小的真值测试,可以提取到单独的VALUE OBJECT中。而这个新对象则可以用来计算另一个对象,看看谓词对那个对象的计算是否为“真”。
换言之,这个新对象就是一个规格。SPECIFICATION(规格)中声明的是限制另一个对象状态的约束,被约束对象可以存在,也可以不存在。SPECIFICATION有多种用途,其中一种体现了最基本的概念,这种用途是:SPECIFICATION可以测试任何对象以检验它们是否满足指定的标准。
因此:
为特殊目的创建谓词形式的显式的VALUE OBJECT。SPECIFICATION就是一个谓词,可用来确 定对象是否满足某些标准。

9.2.4 SPECIFICATION的应用和实现

SPECIFICATION最有价值的地方在于它可以将看起来完全不同的应用功能统一起来。出于以下3个目的中的一个或多个,我们可能需要指定对象的状态。

  • 验证对象,检查它是否能满足某些需求或者是否已经为实现某个目标做好了准备。
  • 从集合中选择一个对象。
  • 指定在创建新对象时必须满足某种需求。

这3种用法(验证、选择和根据要求来创建)从概念层面上来讲是相同的。如果没有诸如SPECIFICATION这样的模式,相同的规则可能会表现为不同的形式,甚至有可能是相互矛盾的形式。这样就会丧失概念上的统一性。通过应用SPECIFICATION模式,我们可以使用一致的模型,尽管在实现时可能需要分开处理。
验证
规格的最简单用法是验证,这种用法也最能直观地展示出它的概念,如图9-14所示。
在这里插入图片描述
选择(或查询)
验证是对一个独立的对象进行测试,检查它是否满足某些标准,然后客户可能根据验证的结论来采取行动。另一种常见需求是根据某些标准从对象集合中选择一个子集。SPECIFICATION概念同样可以在此应用,但是实现问题会有所不同。
SPECIFICATION与REPOSITORY的搭配非常合适,REPOSITORY作为一种构造块机制,提供了对领域对象的查询访问,并且把数据库接口封装起来(参见图9-15)。
在这里插入图片描述
现在的设计有一些问题。最重要的问题是,表结构的细节本应该被隔离到一个映射层中(这个映射层把领域对象关联到关系表),现在却泄漏到了DOMAIN LAYER中。这样一来,这些表结构信息发生了隐性的重复,因此导致对Invoice和Customer对象的修改和维护变得很麻烦,因为现在必须在多个地方跟踪它们的映射变化。
如果无法把SQL语句创建到基础设施中,还可以重写一个专用的查询方法并把它添加到 Invoice Repository中,这样就把SQL语句从领域对象中分离出来了。为了避免在REPOSITORY中嵌入规则,必须采用更为通用的方式来表达查询,这种方式不捕捉规则但是可以通过组合或放臵在 上下文中来表达规则(在这个例子中,使用的是双分派模式)。
在这里插入图片描述
Invoice Specification中的satisfyingElementsFrom(InvoiceRepository)方法被替换为 ,并在Delinquent Invoice Specification中以如下的方式实现:
在这里插入图片描述
这段代码将SQL置于REPOSITORY中,而应该使用哪个查询则由SPECIFICATION来控制。 SPECIFICATION中并没有定义完整的规则,但规则的核心已位于其中。
根据要求来创建(生成)
如果不使用SPECIFICATION,可以编写一个生成器,其中包含可创建所需对象的过程或指令集。 这种代码隐式地定义了生成器的行为。
反过来,我们也可以使用描述性的SPECIFICATION来定义生成器的接口,这个接口就显式地约束了生成器产生的结果。这种方法具有以下几个优点。

  • 生成器的实现与接口分离。SPECIFICATION声明了输出的需求,但没有定义如何得到输出结果。
  • 接口把规则显式地表示出来,因此开发人员无需理解所有操作细节即可知晓生成器会产生什么结果。而如果生成器是采用过程化的方式定义的,那么要想预测它的行为,唯一的途径就是在不同的情况下运行或去研究每行代码。
  • 接口更为灵活,或者说我们可以增强其灵活性,因为需求由客户给出,生成器唯一的职责就是实现SPECIFICATION中的要求。
  • 最后一点也很重要。这种接口更加便于测试,因为接口显式地定义了生成器的输入,而这同时也可用来验证输出。

通过可工作的原型来摆脱开发僵局
有的团队必须要等待另一个团队编写出代码后才可以继续工作。而这两个团队都要等到 代码完全整合后才可以测试组件或从用户那里获取反馈。这种僵局通常可以通过关键组件的模型驱动原型来缓解,即使原型并不满足所有需求也可以。当实现与接口分离时,只要有可以工作的实现,项目工作就可以并行地开展下去。时机成熟的时候,可以用更为高效的实现来替代原型。同时,系统中的其他部分也能在开发期间与原型进行交互。

第10章 柔性设计

在这里插入图片描述

10.1 模式:INTENTION-REVEALING INTERFACES

如果开发人员为了使用一个组件而必须要去研究它的实现,那么就失去了封装的价值。当某个人开发的对象或操作被别人使用时,如果使用这个组件的新的开发者不得不根据其实现来推测其用途,那么他推测出来的可能并不是那个操作或类的主要用途。如果这不是那个组件的用途, 虽然代码暂时可以工作,但设计的概念基础已经被误用了,两位开发人员的意图也是背道而驰。
当我们把概念显式地建模为类或方法时,为了真正从中获取价值,必须为这些程序元素赋予 一个能够反映出其概念的名字。类和方法的名称为开发人员之间的沟通创造了很好的机会,也能够改善系统的抽象。
KentBeck曾经提出通过INTENTION-REVEALINGSELECTOR(释意命名选择器)来选择方法的名称,使名称表达出其目的。设计中的所有公共元素共同构成了接口,每个元素的名称都提供了揭示设计意图的机会。类型名称、方法名称和参数名称组合在一起,共同形成了一个 INTENTION-REVEALING INTERFACE(释意接口)。
因此:
在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目的的。 这样可以使客户开发人员不必去理解内部细节。这些名称应该与UBIQUITOUS LANGUAGE保持一 致,以便团队成员可以迅速推断出它们的意义。在创建一个行为之前先为它编写一个测试,这样 可以促使你站在客户开发人员的角度上来思考它。
所有复杂的机制都应该封装到抽象接口的后面,接口只表明意图,而不表明方式。

10.2 模式:SIDE-EFFECT-FREE FUNCTION

多个规则的相互作用或计算的组合所产生的结果是很难预测的。开发人员在调用一个操作时,为了预测操作的结果,必须理解它的实现以及它所调用的其他方法的实现。如果开发人员不得不“揭开接口的面纱”,那么接口的抽象作用就受到了限制。如果没有了可以安全地预见到结果的抽象,开发人员就必须限制“组合爆炸” ,这就限制了系统行为的丰富性。
返回结果而不产生副作用的操作称为函数。一个函数可以被多次调用,每次调用都返回相同的值。一个函数可以调用其他函数,而不必担心这种嵌套的深度。函数比那些有副作用的操作更易于测试。由于这些原因,使用函数可以降低风险。
因此:
尽可能把程序的逻辑放到函数中,因为函数是只返回结果而不产生明显副作用的操作。严格地把命令(引起明显的状态改变的方法)隔离到不返回领域信息的、非常简单的操作中。当发现了一个非常适合承担复杂逻辑职责的概念时,就可以把这个复杂逻辑移到VALUE OBJECT中,这样可以进一步控制副作用。
SIDE-EFFECT-FREE FUNCTION,特别是在不变的VALUE OBJECT中,允许我们安全地对多个操作进行组合。当通过INTENTION-REVEALING INTERFACE把一个FUNCTION呈现出来的时候,开发人员就可以在无需理解其实现细节的情况下使用它。

10.3 模式:ASSERTION

把复杂的计算封装到SIDE-EFFECT-FREE FUNCTION中可以简化问题,但实体仍然会留有一些有副作用的命令,使用这些ENTITY的人必须了解使用这些命令的后果。在这种情况下,使用ASSERTION(断言)可以把副作用明确地表示出来,使它们更易于处理。
如果操作的副作用仅仅是由它们的实现隐式定义的,那么在一个具有大量相互调用关系的系统中,起因和结果会变得一团糟。理解程序的唯一方式就是沿着分支路径来跟踪程序的执行。封装完全失去了价值。跟踪具体的执行也使抽象失去了意义。
因此:
把操作的后置条件和类及AGGREGATE的固定规则表述清楚。如果在你的编程语言中不能直接编写ASSERTION,那么就把它们编写成自动的单元测试。还可以把它们写到文档或图中(如果符合项目开发风格的话)。

10.4 模式:CONCEPTUAL CONTOUR

把设计元素(操作、接口、类和AGGREGATE)分解为内聚的单元,在这个过程中,你对领域中一切重要划分的直观认识也要考虑在内。在连续的重构过程中观察发生变化和保证稳定的规律性,并寻找能够解释这些变化模式的底层CONCEPTUAL CONTOUR。使模型与领域中那些一致的方面(正是这些方面使得领域成为一个有用的知识体系)相匹配。
我们的目标是得到一组可以在逻辑上组合起来的简单接口,使我们可以用UBIQUITOUS LANGUAGE进行合理的表述,并且使那些无关的选项不会分散我们的注意力,也不增加维护负担。但这通常是通过重构才能得到的结果,很难在前期就实现。而且如果仅仅是从技术角度进行重构, 可能永远也不会出现这种结果;只有通过重构得到更深层的理解,才能实现这样的目标。
设计即使是按照CONCEPTUAL CONTOUR进行,也仍然需要修改和重构。当连续的重构往往只是做出一些局部修改(而不是对模型的概念产生大范围的影响)时,这就是模型已经与领域相吻合的信号。如果遇到了一个需求,它要求我们必须大幅度地修改对象和方法的划分,那么这就在向我们传递这样一条信息:我们对领域的理解还需要精化。它提供了一个深化模型并且使设计变得更具柔性的机会。

10.5 模式:STANDALONE CLASS

低耦合是对象设计的一个基本要素。尽一切可能保持低耦合。把其他所有无关概念提取到对象之外。这样类就变得完全独立了,这就使得我们可以单独地研究和理解它。每个这样的独立类都极大地减轻了因理解MODULE而带来的负担。
尽力把最复杂的计算提取到STANDALONE CLASS(独立的类)中,实现此目的的一种方法是从存在大量依赖的类中将VALUE OBJECT建模出来。
低耦合是减少概念过载的最基本办法。独立的类是低耦合的极致。
消除依赖性并不是说要武断地把模型中的一切都简化为基本类型,这样只会削弱模型的表达 能力。本章要讨论的最后一个模式CLOSURE OF OPERATION(闭合操作)就是一种在减小依赖性的同时保持丰富接口的技术。

10.6 模式:CLOSURE OF OPERATION

当我们对集合中的任意两个元素组合时,结果仍在这个集合中,这就叫做闭合操作。
在适当的情况下,在定义操作时让它的返回类型与其参数的类型相同。如果实现者 (implementer)的状态在计算中会被用到,那么实现者实际上就是操作的一个参数,因此参数和返回值应该与实现者有相同的类型。这样的操作就是在该类型的实例集合中的闭合操作。闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖。
这种模式更常用于VALUE OBJECT的操作。由于ENTITY的生命周期在领域中十分重要,因此我们不能为了解决某一问题而草率创建一个ENTITY。有一些操作是ENTITY类型之下的闭合操作。 我们可以通过查询一个Employee(员工)对象来返回其主管,而返回的将是另一个Employee对 象。但是,ENTITY通常不会成为计算结果。因此,大部分闭合操作都应该到VALUE OBJECT中去寻找。

10.7 声明式设计

声明式设计对于不同的人来说具有不同的意义, 但通常是指一种编程方式— 把程序或程序的一部分写成一种可执行的规格(specification)。使 用声明式设计时,软件实际上是由一些非常精确的属性描述来控制的。

领域特定语言
领域特定语言是一种有趣的方法,它有时也是一种声明式语言。采用这种编码风格时,客户代码是用一种专门为特定领域的特定模型定制的语言编写的。例如,运输系统的语言可能包括cargo(货物)和route(路线)这样的术语,以及一些用于组合这些术语的语法。然后,程序通常会被编译成传统的面向对象语言,由一个类库为这些术语提供实现。
为了精化模型,开发人员需要修改语言。这可能涉及修改语法声明和其他语言解释功能,以及修改底层类库。
此外,用同一种语言实现的应用程序和模型之间是“无缝”的,这一点很有价值。另一个缺点是当模型被修改时,很难对客户代码进行重构,使之与修改之后的模型及与其相关的领域特定语言保持一致。

10.8 声明式设计风格

略~

10.9 切入问题的角度

略~

第11章 应用分析模式

分析模式是一种概念集合,用来表示业务建模中的常见结构。它可能只与一个领域
有关,也可能跨越多个领域。
分析模式的最大作用是借鉴其他项目的经验,把那些项目中有关设计方向和实现结果的广泛讨论与当前模型的理解结合起来。脱离具体的上下文来讨论模型思想不但难 以落地,而且还会造成分析与设计严重脱节的风险,而这一点正是MODEL-DRIVEN DESIGN坚决反对的。

分析模式是很有价值的知识
当你可以幸运地使用一种分析模式时,它一般并不会直接满足你的需求。但它为你的研究提供了有价值的线索,而且提供了明确抽象的词汇。它还可以指导我们的实现,从而省去很多麻烦。
我们应该把所有分析模式的知识融入到知识消化和重构的过程中,从而形成更深刻的理解, 并促进开发。当我们应用一种分析模式时,所得到的结果通常与该模式的文献中记载的形式非常相像,只是因具体情况不同而略有差异。但有时完全看不出这个结果与分析模式本身有关,然而 这个结果仍然是受该模式思想的启发而得到的。
但有一个误区是应该避免的。当使用众所周知的分析模式中的术语时,一定要注意,不管其表面形式的变化有多大,都不要改变它所表示的基本概念。这样做有两个原因,一是模式中蕴含 的基本概念将帮助我们避免问题,二是(也是更重要的原因)使用被广泛理解或至少是被明确解释的术语可以增强UBIQUITOUS LANGUAGE。如果在模型的自然演变过程中模型的定义也发生改变,那么就要修改模型名称了。

第12章 将设计模式应用于模型

12.1 模式:STRATEGY(也称为POLICY)

在这里插入图片描述
定义了一组算法,将每个算法封装起来,并使它们可以互换。STRATEGY允许算法独立于使用它的客户而变化。
当对过程进行建模时,我们经常会发现过程有不止一种合理的实现方式,而如果把所有的可选项都写到过程的定义中,定义就会变得臃肿而复杂,而且可供我们选择的实际行为也会因为混 杂在其他行为中而显得模糊不清。
因此:
我们需要把过程中的易变部分提取到模型的一个单独的“策略”对象中。将规则与它所控制的行为区分开。按照STRATEGY设计模式来实现规则或可替换的过程。策略对象的多个版本表示了完成过程的不同方式。
通常,作为设计模式的STRATEGY侧重于替换不同算法的能力,而当其作为领域模式时,其侧重点则是表示概念的能力,这里的概念通常是指过程或策略规则。
我们把一个Route Specification(路线规格)传递给Routing Service(路线服务),Routing Service的职责是构造一个满足SPECIFICATION的详细的Itinerary。这个SERVICE是一个优化引擎,可以通过调节它来查找最快的路线或最便宜的路线。
在这里插入图片描述
这种设臵看上去似乎没问题,但仔细观察路线代码就会发现,每个计算中都有条件判断,判断最快还是最便宜的逻辑分散在程序各处。当为了做出更精细的航线选择而把新标准添加进来时,麻烦会更多。
解决此问题的一种方法是把这些起调节作用的参数分离到STRATEGY中。这样它们就可以被明确地表示出来,并作为参数传递给Routing Service。
在这里插入图片描述
现在,领域中的一个至关重要的规则明确地显示出来了,也就是在构建Itinerary时用于选择 Leg的基本规则。

12.2 模式:COMPOSITE

在这里插入图片描述
将对象组织为树来表示部分—整体的层次结构。利用COMPOSITE,客户可以对单独的对象和对象组合进行同样的处理。
在对复杂的领域进行建模时,我们经常会遇到由多个部分组成的重要对象,这些部分本身又由其他一些部分组成,依此类推,有时甚至会出现任意深度的嵌套。在一些领域中,各层嵌套在概念上是有区别的,但在另一些领域中,各个部分与它们所组成的整体是完全相同的事物,只是 规模较小一些而已。
定义一个把COMPOSITE的所有成员都包含在内的抽象类型。在容器上实现那些查询信息的方法时,这些方法返回由容器内容所汇总的信息。而“叶”节点则基于它们自己的值来实现这些方法。客户只需使用抽象类型,而无需区分“叶”和容器。

第13章 通过重构得到更深层的理解

13.1 开始重构

获得深层理解的重构可能出现在很多方面。一开始有可能是为了解决代码中的问题——一段复杂或笨拙的代码。但开发人员并没有使用(代码重构所提供的)标准的代码转换,相反,他们认为问题的根源在于领域模型。或许是领域中缺少一个概念,或许是某个关系发生了错误。
与传统重构观点不同的是,即使在代码看上去很整洁的时候也可能需要重构,原因是模型的语言没有与领域专家保持一致,或者新需求不能被自然地添加到模型中。重构的原因也可能来自学习:当开发人员通过学习获得了更深刻的理解,从而发现了一个得到更清晰或更有用的模型的机会。

13.2 探索团队

不管问题的根源是什么,下一步都是要找到一种能够使模型表达变得更清楚和更自然的改进方案。这可能只需要做一些简单、明显的修改,只需几小时即可完成。在这种情况下,所做的修 改类似于传统重构。但寻找新模型可能需要更多时间,而且需要更多人参与。

13.3 借鉴先前的经验

我们没有必要总去做一些无谓的重复工作。用于查找缺失概念或改进模型的头脑风暴过程具有巨大的作用,通过这个过程可以收集来自各个方面的想法,并把这些想法与已有知识结合起来。 随着知识消化的不断开展,就能找到当前问题的答案。
我们可以从书籍和领域自身的其他知识源获得思路。尽管相关领域的人员可能还没有创建出适合运行软件的模型,但他们可能已经把概念很好地组织到了一起,并发现了一些有用的抽象。 把这些知识结合到知识消化过程中,可以更快速地得到更丰富的结果,而且这个结果也更为领域专家们所熟悉。

13.4 针对开发人员的设计

软件不仅仅是为用户提供的,也是为开发人员提供的。开发人员必须把他们编写的代码与系统的其他部分集成到一起。在迭代过程中,开发人员反复修改代码。开发人员应该通过重构得到 更深层的理解,这样既能够实现柔性设计,也能够从这样一个设计中获益。

13.5 重构的时机

如果一直等到完全证明了修改的合理性之后才去修改,那么可能要等待太长时间了。
持续重构渐渐被认为是一种“最佳实践”,但大部分项目团队仍然对它抱有很大的戒心。人们虽然看到了修改代码会有风险,还要花费开发时间,但却不容易看到维持一个拙劣设计也有风险,而且迁就这种设计也要付出代价。想要重构的开发人员往往被要求证明其重构的合理性。虽然这看似合理,但这使得一个本来就很难进行的工作变得几乎不可能完成,而且会限制重构的进行(或者人们只能暗地里进行)。软件开发并不是一个可以完全预料到后果的过程,人们无法准确地计算出某个修改会带来哪些好处,或者是不做某个修改会付出多大代价。
当发生以下情况时,就应该进行重构了:

  • 设计没有表达出团队对领域的最新理解;
  • 重要的概念被隐藏在设计中了(而且你已经发现了把它们呈现出来的方法);
  • 发现了一个能令某个重要的设计部分变得更灵活的机会。

我们虽然应该有这样一种积极的态度,但并不意味着可以随随便便做任何修改。在发布的前一天,就不要进行重构了。不要引入一些只顾炫耀技术能力而没有解决领域核心问题的“柔性设计”。无论一个“更深层的模型”看起来有多好,如果你不能说服领域专家们去使用它,那么就不要引入它。万事都不是绝对的,但如果某个重构对我们有利,那么不妨在这个方向上大胆前进。

13.6 危机就是机遇

传统意义上的重构听起来是一个非常稳定的过程。但通过重构得到更深层理解往往不是这样的。在对模型进行一段时间稳定的改进后,你可能突然有所顿悟,而这会改变模型中的一切。这些突破不会每天都发生,然而很大一部分深层模型和柔性设计都来自这些突破。
这样的情况往往看起来不像是机遇,而更像危机。例如,你突然发现模型中有一些明显的缺陷,在表达方面显示出一个很大的漏洞,或存在一些没有表达清楚的关键区域。或者有些描述是 完全错误的。
这些都表明团队对模型的理解已经达到了一个新的水平。他们现在站在更高的层次上发现了原有模型的弱点。他们可以从这种角度构思一个更好的模型。
通过重构得到更深层理解是一个持续不断的过程。人们发现一些隐含的概念,并把它们明确地表示出来。有些设计部分变得更具有柔性,或许还采用了声明式的风格。开发工作一下子到了突破的边缘,然后开发人员跨越这条界线,得到了一个更深层的模型,接下来又重新开始了稳步的改进过程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【内容简介】 “每个有思想的软件开发者的书架上都应该有这样一本书”——Kent Beck “Eric设法收集了经验丰富的对象设计人员一直使用的一些设计过程,作为一个团队的人们在这些过程中却没能够成功地完成剩下的工作。人们将知识弄得支离破碎……却从来没有将建立领域逻辑的原则组织起来并使其系统化。这本书是非常重要的。”—— Kyle Brown,《Enterprise Java Programming with IBM WebSphere》的作者。 本书涉及的主题具体包括: ●隔离领域●实体、值对象、服务和模块●一个领域对象的生命周期●将过程表示为领域对象●创建没有副作用的函数●总体轮廓●独立的类●扩展说明●应用分析模式●将设计模式与模型相联系●维护模型的完整性●设计领域前景声明●选择重构目标●职责层次●创建可插入的组件框架●结合大比例结构与界限上下文 本书为读者系统地介绍了领域驱动设计方法。书中介绍了大量优秀的设计示例、基于经验的技术以及促进处理复杂领域软件开发的基本原则。本书将设计和开发实践相结合,在介绍领域驱动设计时,还提供了大量的Java示例,这些例子都是从实际中提取出来的,展示了领域驱动设计软件开发中的实际应用。 通过对本书的阅读,读者将获得对领域驱动设计的总体认识,了解领域驱动设计中涉及的关键原则、术语和推断。本书介绍的经验和标准模式将为开发团队提供一种通用语言。另外,书中还介绍了如何在领域模型中进行重构,如何与敏捷开发进行集成,如何获得对领域更深的认识并增进领域专家和程序员之间的交流等。并在此基础上,介绍了在复杂系统和较大组织中进行的领域驱动设计

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值