深层模型和柔性设计并非唾手可得。要想取得进展,必须学习大量领域知识并进行充分的讨论,还需要经历大量的尝试和失败。但有时我们也能从中获得一些优势。一位经验丰富的开发人员在研究领域问题时,如果发现了他所熟悉的某种职责或某个关系网,他会想起以前这个问题是如何解决的。以前尝试过哪些模型?哪些是有效的?在实现中有哪些难题?它们是如何解决的?先前经历过的尝试和失败会突然间与新的情况联系起来。这些模式当中有一些已经记载到文献中供大家分享,这样我们就可以借鉴这些积累的经验。
利用分析模式和设计模式,可以避免一些代价高昂的尝试和失败过程,而直接从一个已经具有良好表达力和易实现的模型开始工作,并解决了一些可能难于学习的微妙的问题。我们可以从这样一个起点来重构和试验。然而,它们并不是现成的解决方案。
应用分析模式
在《分析模式》一书中,Martin Fowler这样定义分析模式[Fowler 1997, p. 8]:分析模式是一种表示业务建模中的常见构造的概念集合,用来表示业务建模中的常见结构。它可能只与一个领域有关,也可能跨越多个领域。
Fowler所提出的分析模式来自于实践经验,因此只要用在合适的情形下,它们会非常实用。对于那些面对着具有挑战性领域的人们,这些模式为他们的迭代开发过程提供了一个非常有价值的起点。"分析模式"这个名字本身就强调了其概念本质。分析模式并不是技术解决方案,他们只是些参考,用来指导人们设计特定领域中的模型。
但从这个名字中我们看不出分析模式也讨论了大量实现问题,包括一些代码。Fowler知道,在不考虑实际设计的情况下进行单纯的分析是有缺陷的。下面举一个很有趣的例子,在这个例子中,Fowler用更长远的眼光审视了模型选择的意义——考虑在部署之后,模型选择对系统长期维护的影响[Fowler 1997, p. 151]。
在一个成熟的项目上,模型选择往往是根据实用经验做出的。人们已经尝试了各种组件的多种实现方法。其中的一些实现已经被采用,有些甚至已经到了维护阶段。这些经验可以帮助人们避免很多问题。**分析模式的最大作用是借鉴其他项目的经验,把那些项目中有关设计方向和实现结果的广泛讨论与当前模型的理解结合起来。**脱离具体的上下文来讨论模型思想不但难以落地,而且还会造成分析与设计严重脱节的风险,而这一点正是MODEL-DRIVEN DESIGN坚决反对的。用实际的例子比用单纯的抽象描述能够更好地解释分析模式的原则和应用。
本章的目的不是对分析模式进行归纳分类,本章的重点是说明如何将它们集成到领域驱动的设计过程中。
分析模式的价值
当你可以幸运地使用一种分析模式时,它一般并不会直接满足你的需求。但它为你的研究提供了有价值的线索,而且提供了明确抽象的词汇。它还可以指导我们的实现,从而省去很多麻烦。
我们应该把所有分析模式的知识融入到知识消化和重构的过程中,从而形成更深刻的理解,并促进开发。当我们应用一种分析模式时,所得到的结果通常与该模式的文献中记载的形式非常相像,只是因具体情况不同而略有差异。但有时完全看不出这个结果与分析模式本身有关,然而这个结果仍然是受该模式思想的启发而得到的。
但有一个误区是应该避免的。当使用众所周知的分析模式中的术语时,一定要注意,不管其表面形式的变化有多大,都不要改变它所表示的基本概念。这样做有两个原因,一是模式中蕴含的基本概念将帮助我们避免问题,二是(也是更重要的原因)使用被广泛理解或至少是被明确解释的术语可以增强UBIQUITOUS LANGUAGE。如果在模型的自然演变过程中模型的定义也发生改变,那么就要修改模型名称了。
很多对象模型都有文献资料可查,其中有些对象模型专门用于某个行业中的某种应用,而有些则是通用模型。大部分对象模型都有助于开阔思路,但只有为数不多的一些模型精辟地阐述了选择这些模式的原理和使用的结果,而这些才是分析模式的精华所在。这些精化后的分析模式大部分都很有价值,有了它们,可以免去一次次的重复开发工作。尽管我们不大可能归纳出一个包罗万象的分析模式类目,但针对具体行业的类目还是能够开发出来的。而且在一些跨越多个应用的领域中适用的模式可以被广泛共享。
这种对已组织好的知识的重复利用完全不同于通过框架或组件进行的代码重用,但是二者唯一的共同点是它们都提供了一种新思路的萌芽,而这种新思路先前可能并不十分明晰。一个模型,甚至一个通用框架,都是一个完整的整体,而分析则相当于一个工具包,它被应用于模型的一些部分。分析模式专注于一些最关键和最艰难的决策,并阐明了各种替代和选择方案。它们提前预测了一些后期结果,而如果单靠我们自己去发现这些结果,可能会付出高昂的代价。
将设计模式应用于模型
设计模式(design pattern)——设计模式是对一些互相交互的对象和类的描述,我们通过定制这些对象和类来解决特定上下文中的一般设计问题[Gamma et al. 1995, p. 3]。
到目前为止,本书所探讨的模式都是专门用来在MODEL-DRIVEN DESIGN的上下文中解决领域模型的问题。但实际上,大部分已发布的模式都更侧重于解决技术问题。设计模式与领域模式之间有什么区别?《设计模式》这部经典著作的作者为初学者指出了以下事实[Gamma et al. 1995, p. 3]:
立场不同会影响人们如何看待什么是模式以及什么不是模式。一个人所认为的模式在另一个人看来可能是基本构造块。本书将在一定的抽象层次上讨论模式。设计模式并不是指像链表和散列表那样可以被封装到类中并供人们直接重用的设计,也不是用于整个应用程序或子系统的复杂的、领域特定的设计。本书中的设计模式是对一些交互的对象和类的描述,我们通过定制这些对象和类来解决特定上下文中的一般设计问题。
在《设计模式》中,有些(但并非所有)模式可用作领域模式,但在这样使用的时候,需要变换一下重点。《设计模式》中的设计模式把相关设计元素归为一类,这些元素能够解决在各种上下文中经常遇到的问题。这些模式的动机以及模式本身都是从纯技术角度描述的。但这些元素中的一部分在更广泛的领域和设计上下文中也适用,因为这些元素所对应的基本概念在很多领域中都会出现。
除了《设计模式》中介绍的模式以外,近年来还出现了其他很多技术设计模式。有些模式反映了在一些领域中出现的深层概念。这些模式都有很大的利用价值。为了在领域驱动设计中充分利用这些模式,我们必须同时从两个角度看待它们:从代码的角度来看它们是技术设计模式,从模型的角度来看它们就是概念模式。
我们将把《设计模式》所介绍的特定模式作为样例,来说明如何将人们所认为的设计模式应用到领域模型中,而且这个例子还将澄清技术设计模式与领域模式之间的区别。本章还将通过COMPOSITE(组合)和STRATEGY(策略)这两种模式演示如何通过改变思考方式,用一些经典的设计模式来解决领域问题。
模式:STRATEGY(也称为POLICY)
领域模型包含一些并非用于解决技术问题的过程,将它们包含进来是因为它们对处理问题领域具有实际的价值。当必须从多个过程中进行选择时,选择的复杂性再加上多个过程本身的复杂性会使局面失去控制。
当对过程进行建模时,我们经常会发现过程有不止一种合理的实现方式,而如果把所有的可选项都写到过程的定义中,定义就会变得臃肿而复杂,而且可供我们选择的实际行为也会因为混杂在其他行为中而显得模糊不清。
我们希望把这些选择从过程的主体概念中分离出来,这样既能够看清主体概念,也能更清楚地看到这些选择。软件设计社区中众所周知的STRATEGY模式就是为了解决这个问题的,虽然它的侧重点在于技术方面。这里,我们把它当成模型中的一个概念来使用,并在该模型的代码实现中把它反映出来。我们同样也需要把过程中极易发生变化的部分与那些更稳定的部分分离开。
我们需要把过程中的易变部分提取到模型的一个单独的"策略"对象中。将规则与它所控制的行为区分开。按照STRATEGY设计模式来实现规则或可替换的过程。策略对象的多个版本表示了完成过程的不同方式。
通常,作为设计模式的STRATEGY侧重于替换不同算法的能力,而当其作为领域模式时,其侧重点则是表示概念的能力,这里的概念通常是指过程或策略规则。
我们在领域层中使用技术设计模式时,必须认识到这样做的另外一种动机,也是它的另一层含义。当所使用的STRATEGY对应于某种实际的业务策略时,模式就不再仅仅是一种有用的实现技术了(但它在实现方面的价值并未改变)。
设计模式的结论也完全适用于领域层。例如,在《设计模式》一书中,Gamma等人指出客户必须知道不同的STRATEGY,这也是建模的一个关注点。如果单纯从实现上来考虑,使用策略可能会增加系统中对象的数目。如果这是个问题,可以把STRATEGY实现为无状态对象,以便在上下文中进行共享,从而减小开销。《设计模式》中对实现方法的全面讨论在这里也适用,这是因为我们仍然在使用STRATEGY,只是动机稍有不同,这会对我们的选择产生一些影响,但设计模式中的经验仍然是可以借鉴的。
模式:COMPOSITE
在对复杂的领域进行建模时,我们经常会遇到由多个部分组成的重要对象,这些部分本身又由其他一些部分组成,依此类推,有时甚至会出现任意深度的嵌套。在一些领域中,各层嵌套在概念上是有区别的,但在另一些领域中,各个部分与它们所组成的整体是完全相同的事物,只是规模较小一些而已。
当嵌套容器的关联性在模型中没有反映出来时,公共行为必然会在层次结构的每一层重复出现,而且嵌套也变得僵化(例如,容器通常不能包含同一层中的其他容器,而且嵌套的层数也是固定的)。客户必须通过不同的接口来处理层次结构中的不同层,尽管这些层在概念上可能没有区别。通过层次结构来递归地收集信息也变得非常复杂。
**当在领域中应用任何一种设计模式时,首先关注的问题应该是模式的意图是否确实适合领域概念。**以递归的方式遍历一些相互关联对象确实比较方便,但它们是否真的存在整体—部分层次结构?你是否发现可以通过某种抽象方式把所有部分都归到同一概念类型中?如果你确实发现了这种抽象方式,那么使用COMPOSITE可以令模型的这些部分变得更清晰,同时使你能够借助设计模式所提供的那些经过深思熟虑的设计及实现的考量。
定义一个把COMPOSITE的所有成员都包含在内的抽象类型。在容器上实现那些查询信息的方法时,这些方法返回由容器内容所汇总的信息。而"叶"节点则基于它们自己的值来实现这些方法。客户只需使用抽象类型,而无需区分"叶"和容器。
相对而言,这是一种明显的结构层面上的模式,但设计人员通常不会主动地充实它的操作方面。COMPOSITE模式在每个结构层上都提供了相同的行为,而且无论是较小的部分还是较大的部分,都可以对这些部分提出一些有意义的问题,这些问题能够透明地反映出它们的构成情况。这种严格的对称是组合模式具有强大能力的关键所在。
设计模式应该仅仅在需要的时候才使用。
为什么没有介绍FLYWEIGHT
FLYWEIGHT虽然是设计模式的一个典型的例子,却并不适用于领域模型。
当一个VALUE OBJECT集合(其中的值对象数目有限)被多次使用的时候(如房屋规划中电源插座的例子),那么把它们实现为FLYWEIGHT可能是有意义的。这是一个适用于VALUE OBJECT(但不适用于ENTITY)的实现选择。COMPOSITE模式与它的不同之处在于,组合模式的概念对象是由其他概念对象组成的。这使得组合模式既适用于模型,也适用于实现,这是领域模式的一个基本特征。
本文并不打算把那些可以当作领域模式使用的设计模式完整地列出来。虽然没有一个把"解释器"(interpreter)用作领域模式的例子,但也不能断言解释器不适用于任何一种领域概念。把设计模式用作领域模式的唯一要求是这些模式能够描述关于概念领域的一些事情,而不仅仅是作为解决技术问题的技术解决方案。
参考
《领域驱动设计 软件核心复杂性应对之道》 Eric Evans 著, 赵俐 盛海艳 刘霞 等译, 任发科 审校