Twelve. Relating Design Patterns to the Model
The patterns explored in this book so far are intended specifically for solving problems in a domain model in the context of a MODEL-DRIVEN DESIGN. Actually, though, most of the patterns published to date are more technical in focus. What is the difference between a design pattern and a domain pattern? For starters, the authors of the seminal book, Design Patterns, had this to say:
到目前为止,本书所探讨的模式都是专门用来在 MODEL-DRIVEN DESIGN 的上下文中解决领域模型的问题。但实际上,大部分已发布的模式都更侧重于解决技术问题。设计模式与领域模式之间有什么区别?《设计模式》这部经典著作的作者为初学者指出了以下事实[Gamma et al.1995, p. 3]:
Point of view affects one’s interpretation of what is and isn’t a pattern. One person’s pattern can be another person’s primitive building block. For this book we have concentrated on patterns at a certain level of abstraction. Design patterns are not about designs such as linked lists and hash tables that can be encoded in classes and reused as is. Nor are they complex, domain-specific designs for an entire application or subsystem. The design patterns in this book are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context. [Gamma et al. 1995, p. 3]
立场不同会影响人们如何看待什么是模式以及什么不是模式。一个人所认为的模式在另一个人看来可能是基本构造块。本书将在一定的抽象层次上讨论模式。设计模式并不是指像链表和散列表那样可以被封装到类中并供人们直接重用的设计,也不是用于整个应用程序或子系统的复杂的、领域特定的设计。本书中的设计模式是对一些交互的对象和类的描述,我们通过定制这些对象和类来解决特定上下文中的一般设计问题。
Some, not all, of the patterns in Design Patterns can be used as domain patterns. Doing so requires a shift in emphasis. Design Patterns presents a catalog of design elements that have solved problems commonly encountered in a variety of contexts. The motivations of these patterns and the patterns themselves are presented in purely technical terms. But a subset of these elements can be applied in the broader context of domain modeling and design, because they correspond to general concepts that emerge in many domains.
在《设计模式》中,有些(但并非所有)模式可用作领域模式,但在这样使用的时候,需要变换一下重点。《设计模式》中的设计模式把相关设计元素归为一类,这些元素能够解决在各种上下文中经常遇到的问题。这些模式的动机以及模式本身都是从纯技术角度描述的。但这些元素中的一部分在更广泛的领域和设计上下文中也适用,因为这些元素所对应的基本概念在很多领域中都会出现。
In addition to those in Design Patterns, there have been many other technical design patterns presented over the years. Some of them correspond to deep concepts that emerge in domains. It would be nice to draw on this work. To make use of such patterns in domain-driven design, we have to look at the patterns on two levels simultaneously. On one level, they are technical design patterns in the code. On the other level, they are conceptual patterns in the model.
除了《设计模式》中介绍的模式以外,近年来还出现了其他很多技术设计模式。有些模式反映了在一些领域中出现的深层概念。这些模式都有很大的利用价值。为了在领域驱动设计中充分利用这些模式,我们必须同时从两个角度看待它们:从代码的角度来看它们是技术设计模式,从模型的角度来看它们就是概念模式。
A sample of specific patterns from Design Patterns will show how a pattern conceived as a design pattern can be applied in the domain model, and it will clarify the distinction between a technical design pattern and a domain pattern. COMPOSITE and STRATEGY demonstrate how some of the classic design patterns can be applied to domain problems by thinking about them in a different way. . . .
我们将把《设计模式》所介绍的特定模式作为样例,来说明如何将人们所认为的设计模式应用到领域模型中,而且这个例子还将澄清技术设计模式与领域模式之间的区别。本章还将通过 COMPOSITE(组合)和 STRATEGY(策略)这两种模式演示如何通过改变思考方式,用一些经典的设计模式来解决领域问题。
Define a family of algorithms, encapsulate each one, and make them interchangeable. STRATEGY lets the algorithm vary independently from clients that use it. [Gamma et al. 1995]
定义了一组算法,将每个算法封装起来,并使它们可以互换。STRATEGY 允许算法独立于使用它的客户而变化。[Gamma et al. 1995]
Domain models contain processes that are not technically motivated but actually meaningful in the problem domain. When alternative processes must be provided, the complexity of choosing the appropriate process combines with the complexity of the multiple processes themselves, and things get out of hand.
领域模型包含一些并非用于解决技术问题的过程,将它们包含进来是因为它们对处理问题领域具有实际的价值。当必须从多个过程中进行选择时,选择的复杂性再加上多个过程本身的复杂性会使局面失去控制。
When we model processes, we often realize that there is more than one legitimate way of doing them. As we start to describe these options, our definition of the process becomes clumsy and complicated. The actual behavioral alternatives we are choosing between are obscured as they are mixed in with the rest of the behavior.
当对过程进行建模时,我们经常会发现过程有不止一种合理的实现方式,而如果把所有的可选项都写到过程的定义中,定义就会变得臃肿而复杂,而且可供我们选择的实际行为也会因为混杂在其他行为中而显得模糊不清。
We would like to separate this variation from the main concept of the process. Then we would be able to see both the main process and the options more clearly. The STRATEGY pattern, already well established in the software design community, addresses this very issue, though the focus is technical. Here it is being applied as a concept in a model and reflected in the code implementation of that model. There is the same need to decouple the highly variable part of the process from the more stable part.
我们希望把这些选择从过程的主体概念中分离出来,这样既能够看清主体概念,也能更清楚地看到这些选择。软件设计社区中众所周知的 STRATEGY 模式就是为了解决这个问题的,虽然它的侧重点在于技术方面。这里,我们把它当成模型中的一个概念来使用,并在该模型的代码实现中把它反映出来。我们同样也需要把过程中极易发生变化的部分与那些更稳定的部分分离开。
Therefore:
因此:
Factor the varying part of a process into a separate “strategy” object in the model. Factor apart a rule and the behavior it governs. Implement the rule or substitutable process following the STRATEGY design pattern. Multiple versions of the strategy object represent different ways the process can be done.
我们需要把过程中的易变部分提取到模型的一个单独的“策略”对象中。将规则与它所控制的行为区分开。按照 STRATEGY 设计模式来实现规则或可替换的过程。策略对象的多个版本表示了完成过程的不同方式。
Whereas the conventional view of STRATEGY as a design pattern focuses on the ability to substitute different algorithms, its use as a domain pattern focuses on its ability to express a concept, usually a process or a policy rule.
通常,作为设计模式的 STRATEGY 侧重于替换不同算法的能力,而当其作为领域模式时,其侧重点则是表示概念的能力,这里的概念通常是指过程或策略规则。
Example: Route-Finding Policies
示例路线查找(Route-Finding)策略
A Route Specification is being passed to a Routing Service, whose job is to construct a detailed Itinerary that satisfies the SPECIFICATION. This SERVICE is an optimization engine that can be tuned to find either the fastest route or the cheapest one.
我们把一个 Route Specification(路线规格)传递给 Routing Service(路线服务), Routing Service 的职责是构造一个满足 SPECIFICATION 的详细的 Itinerary。这个 SERVICE 是一个优化引擎,可以通过调节它来查找最快的路线或最便宜的路线。
A SERVICE interface with options will need conditional logic.
This setup looks OK, but a detailed look at the routing code would reveal conditionals in every computation, making the decision between fastest or cheapest appear all over the place. More trouble will come when new criteria are added to make more subtle choices between routes.
这种设置看上去似乎没问题,但仔细观察路线代码就会发现,每个计算中都有条件判断,判断最快还是最便宜的逻辑分散在程序各处。当为了做出更精细的航线选择而把新标准添加进来时,麻烦会更多。
One approach is to separate those tuning parameters into STRATEGIES. Then they can be represented explicitly, passed into the Routing Service as a parameter.
解决此问题的一种方法是把这些起调节作用的参数分离到 STRATEGY 中。这样它们就可以被明确地表示出来,并作为参数传递给 Routing Service。
The Routing Service now handles all requests in the same, unconditional way, looking for a sequence of Legs with a low magnitude, as computed by the Leg Magnitude Policy.
现在,Routing Service 就可以用一种完全相同的、无需进行条件判断的方式来处理所有请求了,它按照 Leg Magnitude Policy(航段规模策略)的计算,找出一系列规模较小的 Leg(航段)。
This design has the advantages that motivate the STRATEGY pattern in Design Patterns. On the level of application versatility and flexibility, the behavior of the Routing Service can now be controlled and extended by installing an appropriate Leg Magnitude Policy. The STRATEGIES illustrated in Figure 12.2 (fastest or cheapest) are only the most obvious ones. Combinations that balance speed and cost are likely. There may be other factors altogether, such as a bias toward booking cargo on the company’s own transports rather than subcontracting to carry them on the transports of other shipping companies. These modifications could have been made without resorting to STRATEGIES, but the logic would have wound through the internals of the Routing Service and bloated its interface. The decoupling does make it clear and easily testable.
这种设计具有《设计模式》中所介绍的 STRATEGY 模式的优点。按这种思路设计的应用程序可以提供丰富的功能,同时也很灵活,现在,可以通过安装适当的 Leg Magnitude Policy 来控制和扩展 Routing Service 的行为。图 12-2 中显示的只是最明显的两种 STRATEGY(最快或最便宜)。可能还会有一些在速度和成本之间进行权衡考虑的组合策略。也可以加进其他的因素,例如,在预订货物时优先选择公司自己的运输系统,而不是外包给其他运输公司。不使用 STRATEGY 模式同样能实现这些修改,但必须将逻辑添加到 Routing Service 的内部(这会是一个麻烦的过程),而且这些逻辑会使接口变得臃肿。解耦确实令 Routing Service 更清楚且易于测试。
Options determined by choice of STRATEGY (POLICY) passed as argument
A fundamentally important rule in the domain, the basis of choosing one Leg over another when building an Itinerary, is now explicit and distinct. It conveys the knowledge that a specific attribute (potentially derived) of an individual leg, boiled down to a single number, is the basis for routing. This makes possible a simple statement in the language of the domain that defines the Routing Service’s behavior: The Routing Service chooses an Itinerary with a minimum total magnitude of the Legs based on the chosen STRATEGY.
现在,领域中的一个至关重要的规则明确地显示出来了,也就是在构建 Itinerary 时用于选择 Leg 的基本规则。它传达了这样一个知识:路线选择的基础是航段的一个特定属性(有可能是派生属性),这个属性最后可归结为一个数字。这样,我们就能够通过领域语言很简单地定义 Routing Service 的行为:Routing Service 根据所选的 STRATEGY 来选择 Leg 总规模最小的 Itinerary。
Note: This discussion implies that the Routing Service is actually evaluating Legs as it searches for an Itinerary. This approach is conceptually straightforward, and it could make a reasonable prototype implementation, but it is probably unacceptably inefficient. This application will be taken up again in Chapter 14, “Maintaining Model Integrity,” where the same interface will be used with a completely different implementation of the Routing Service.
说明:以上讨论暗示了一件事。Routing Service 在查找 Itinerary 时实际上会计算 Leg 的规模。这种方法在概念上比较直接,而且可以生成一个合理的原型实现,但它的效率可能令人无法接受。第 14 章会再次讨论这个应用程序,其将使用相同的接口,但采用完全不同的 Routing Service 实现。
When we use the technical design pattern in the domain layer, we have to add an additional motivation, another layer of meaning. When the STRATEGY corresponds to an actual business strategy or policy, the pattern becomes more than just a useful implementation technique (though that too is valuable as far as it goes).
我们在领域层中使用技术设计模式时,必须认识到这样做的另外一种动机,也是它的另一层含义。当所使用的 STRATEGY 对应于某种实际的业务策略时,模式就不再仅仅是一种有用的实现技术了(但它在实现方面的价值并未改变)。
The consequences of the design pattern fully apply. For example, in Design Patterns, Gamma et al. point out that clients must be aware of different STRATEGIES, which is also a modeling concern. A concern purely of implementation is that STRATEGIES can increase the number of objects in the application. If that is a problem, the overhead can be reduced by implementing STRATEGIES as stateless objects that contexts can share. The extensive discussion of implementation approaches in Design Patterns all applies here. This is because we are still using a STRATEGY. Our motivations are partially different, which will affect some choices, but the experience embedded in the design pattern is at our disposal.
设计模式的结论也完全适用于领域层。例如,在《设计模式》一书中,Gamma 等人指出客户必须知道不同的 STRATEGY,这也是建模的一个关注点。如果单纯从实现上来考虑,使用策略可能会增加系统中对象的数目。如果这是个问题,可以把 STRATEGY 实现为无状态对象,以便在上下文中进行共享,从而减小开销。《设计模式》中对实现方法的全面讨论在这里也适用,这是因为我们仍然在使用 STRATEGY,只是动机稍有不同,这会对我们的选择产生一些影响,,但设计模式中的经验仍然是可以借鉴的。
Compose objects into tree structures to represent part-whole hierarchies. COMPOSITE lets clients treat individual objects and compositions of objects uniformly. [Gamma et al. 1995]
We often encounter, while modeling complex domains, an important object that is composed of parts, which are themselves made up of parts, which are made up of parts—occasionally even nesting to arbitrary depth. In some domains, each of these levels is conceptually distinct, but in other cases, there is a sense in which the parts are the same kind of thing as the whole, only smaller.
在对复杂的领域进行建模时,我们经常会遇到由多个部分组成的重要对象,这些部分本身又由其他一些部分组成,依此类推,有时甚至会出现任意深度的嵌套。在一些领域中,各层嵌套在概念上是有区别的,但在另一些领域中,各个部分与它们所组成的整体是完全相同的事物,只是规模较小一些而已。
When the relatedness of nested containers is not reflected in the model, common behavior has to be duplicated at each level of the hierarchy, and nesting is rigid (for example, containers can’t usually contain other containers at their own level, and the number of levels is fixed). Clients must deal with different levels of the hierarchy through different interfaces, even though there may be no conceptual difference they care about. Recursion through the hierarchy to produce aggregated information is very complicated.
当嵌套容器的关联性没有在模型中反映出来时,公共行为必然会在层次结构的每一层重复出现,而且嵌套也变得僵化(例如,容器通常不能包含同一层中的其他容器,而且嵌套的层数也是固定的)。客户必须通过不同的接口来处理层次结构中的不同层,尽管这些层在概念上可能没有区别。通过层次结构来递归地收集信息也变得非常复杂。
When applying any design pattern in the domain, the first concern should be whether the pattern idea really is a good fit for the domain concept. It might be convenient to move recursively through some associated objects, but is there a true whole-part hierarchy? Have you found an abstraction under which all the parts truly are the same conceptual type? If you have, COMPOSITE will make those aspects of the model clearer, while allowing you to tap into the carefully thought-out design and implementation considerations of the design pattern.
当在领域中应用任何一种设计模式时,首先关注的问题应该是模式的意图是否确实适合领域概念。以递归的方式遍历一些相互关联对象确实比较方便,但它们是否真的存在整体—部分层次结构?你是否发现可以通过某种抽象方式把所有部分都归到同一概念类型中?如果你确实发现了这种抽象方式,那么使用 COMPOSITE 可以令模型的这些部分变得更清晰,同时使你能够借助设计模式所提供的那些经过深思熟虑的设计及实现的考量。
Therefore:
因此:
Define an abstract type that encompasses all members of the COMPOSITE. Methods that return information are implemented on containers to return aggregated information about their contents. “Leaf” nodes implement those methods based on their own values. Clients deal with the abstract type and have no need to distinguish leaves from containers.
定义一个把 COMPOSITE 的所有成员都包含在内的抽象类型。在容器上实现那些查询信息的方法时,这些方法返回由容器内容所汇总的信息。而“叶”节点则基于它们自己的值来实现这些方法。客户只需使用抽象类型,而无需区分“叶”和容器。
This is a relatively obvious pattern on the structural level, but designers often do not push themselves to flesh out the operational level of the pattern. The COMPOSITE offers the same behavior at every structural level, and meaningful questions can be asked of small or large parts that transparently reflect their makeup. That rigorous symmetry is the key to the power of the pattern.
相对而言,这是一种明显的结构层面上的模式,但设计人员通常不会主动地充实它的操作方面。COMPOSITE 模式在每个结构层上都提供了相同的行为,而且无论是较小的部分还是较大的部分,都可以对这些部分提出一些有意义的问题,这些问题能够透明地反映出它们的构成情况。这种严格的对称是组合模式具有强大能力的关键所在。
Example: Shipment Routes Made of Routes
示例由 Route 构成的 Shipment Route
A complete cargo shipment route is complicated. First the container must be trucked to a railhead, then carried to a port, then transported on a ship to another port, possibly transferred to other ships, and finally transported by ground on the other end.
完整的货物运输路线是很复杂的。首先,必须用卡车把集装箱运输到铁路终点站,然后运送到港口,之后用货轮运输到另一个港口,中间可能还会换船,最后还要进行地面运输才能到达目的地。
A schematic of a “route” made up of “legs”
An application development team has created an object model to express these arbitrarily long strings of legs that assemble into a route.
一个应用开发团队创建了一个对象模型,它表示了一个航线可以由任意多个航段组成。
A class diagram of a Route made up of Legs
Using this model, the developers are able to create Route objects based on booking requests. They are able to process the Legs into the operational plan for the step-by-step handling of the cargo. Then they discover something.
利用这个模型,开发人员可以根据预订请求来创建 Route 对象。他们可以把这些 Leg 组织为一步一步运输货物的操作计划。在这个过程中他们发现了一些问题。
The developers had always thought of a route as an arbitrary, un-differentiated string of legs.
开发人员原来一直认为航线是由任意多个航段组成的,而各个航段之间并没有什么区别。
The developers’ conception of a route
It turns out the domain experts see the route as a sequence of five logical segments.
而事实上领域专家把航线看成是由 5 个逻辑段组成的序列。
The business experts’ conception of a route
Among other things, these subroutes may be planned at different times by different people, so they have to be viewed as distinct. And on closer inspection, the “door legs” are quite different from the other legs, involving locally hired trucks or even customer haulage, in contrast to the elaborately scheduled rail and ship transports.
其他问题先不考虑,这些小段的航线可能是由不同的人在不同时间规划的,因此必须区别对待。通过更仔细的研究可以发现,“门航段”(door leg)与其他航段大不相同,它涉及在当地雇用卡车甚至是客户运输,这与详细计划的铁路和货船运输完全不同。
An object model reflecting all these distinctions starts to get complicated.
反映了所有这些区别的对象模型渐渐变得复杂起来。
The elaborated class diagram of Route
Structurally the model isn’t so bad, but the uniformity of processing the operational plan is lost, so the code, or even a description of behavior, becomes much more complicated. Other complications begin to surface, too. Any traversal of a route involves multiple collections of different types of objects.
从结构上看这个模型并不是很差,但在操作计划的处理上失去了一致性,因此代码(甚至是行为的描述)变得复杂得多。其他复杂之处也渐渐显现。任何一条航线的遍历都涉及不同类型对象的多个集合。
Enter COMPOSITE. It would be nice, for certain clients, to treat the different levels in this construct uniformly, as routes made up of routes. Conceptually this view is sound. Every level of Route is a movement of a container from one point to another, all the way down to an individual leg. (See Figure 12.8.)
运用 COMPOSITE 模式能使特定客户在不同层上都使用这种构造进行统一的处理,因为大的航线是由小段的航线构成的。这种视图在概念上也是合理的。每一层 Route 都是集装箱从一个地点到另一个地点的移动,最后都归结为一个独立的航段(参见图 12-8)。
A class diagram using COMPOSITE
Now, the static class diagram does not tell us as much about how door legs and other segments fit together as the previous one did. But the model is more than a static class diagram. We’ll convey assembly information through other diagrams (see Figure 12.9) and through the (now much simpler) code. This model captures the deep relatedness of all these different kinds of “Route.” Generating the operational plan is simple again, as are other route-traversing operations.
与前面那个类图不同,从现在这个静态类图看不出来门航段是如何与其他航段组合在一起的。但模型并不只包含静态类图。我们将通过其他的图(参见图 12-9)和代码(现在代码简单多了)来表示这些航段的组合信息。这个模型抓住了所有这些不同类型 Route 的深层关联性。生成操作计划的工作再次变得简单了,而且其他路线遍历操作也变得简单了。
Instances representing a complete Route
With a route made of other routes, pieced together end to end to get from one place to another, you can have route implementations of varying detail. You can chop off the end of a route and splice on a new ending, you can have arbitrary nesting of detail, and you can exploit all sorts of possibly useful options.
利用这种“由航线组成航线”的方法,我们可以把各个航线的端点连接到一起来得到从一个地点到另一个地点的航线,从而可以实现各种不同的航线。我们可以把航线的一端截去,再拼接一段新的航线,我们可以有任何细节的嵌套,而且可以充分利用一切可能有用的选项。
Of course, we don’t yet need such options. And before we needed those route segments and distinct door legs, we were doing just fine without COMPOSITE. A design pattern should be applied only when it is needed.
当然,我们现在还不需要这些选择。当不需要这些航线分段和不同的“门航段”时,不使用 COMPOSITE 模式也能很好地工作。设计模式应该仅仅在需要的时候才使用。
Because I referred to the FLYWEIGHT pattern earlier (in Chapter 5), you might have assumed that it is an example of a pattern to be applied to domain models. In fact, FLYWEIGHT is a good example of a design pattern that has no correspondence to the domain model.
由于第 5 章中提到过 FLYWEIGHT 模式,因此你可能认为它是一种适用于领域模型的模式。事实上,FLYWEIGHT 虽然是设计模式的一个典型的例子,却并不适用于领域模型。
When a limited set of VALUE OBJECTS is used many times (as in the example of electrical outlets in a house plan), it may make sense to implement them as FLYWEIGHTS. This is an implementation option available for VALUE OBJECTS and not for ENTITIES. Contrast this with COMPOSITE, in which conceptual objects are composed of other conceptual objects. In that case, the pattern applies to both model and implementation, which is an essential trait of a domain pattern.
当一个 VALUE OBJECT 集合(其中的值对象数目有限)被多次使用的时候(如房屋规划中电源插座的例子),那么把它们实现为 FLYWEIGHT 可能是有意义的。这是一个适用于 VALUE OBJECT(但不适用于 ENTITY)的实现选择。COMPOSITE 模式与它的不同之处在于,组合模式的概念对象是由其他概念对象组成的。这使得组合模式既适用于模型,也适用于实现,这是领域模式的一个基本特征。
I’m not going to try to compile a list of the design patterns that can be used as domain patterns. Although I can’t think of an example of using an interpreter as a domain pattern, I’m not prepared to say that there is no conception of any domain that would fit. The only requirement is that the pattern should say something about the conceptual domain, not just be a technical solution to a technical problem.
我并不打算把那些可以当作领域模式使用的设计模式完整地列出来。虽然我想不出一个把“解释器”(interpreter)用作领域模式的例子,但我也不能断言解释器不适用于任何一种领域概念。把设计模式用作领域模式的唯一要求是这些模式能够描述关于概念领域的一些事情,而不仅仅是作为解决技术问题的技术解决方案。