Composition of Design Patterns
The development of applications using design patterns as design components requires a careful look at composition techniques. In this chapter we briefly review several techniques for composing design patterns. These techniques can be categorized as
Behavioral composition techniques
Structural composition techniques
The behavioral techniques are based on object interaction specifications to show how instantiations of patterns can be composed, whereas structural techniques are based on the static architectural specifications of composed instantiated patterns using class diagrams. Although we adopt in the following chapters the structural approach, a hybrid technique showing both structural composition and behavioral composition may evolve as the more comprehensive approach for specifying how instantiated patterns can be composed.
Analysis of pattern behaviors is an important topic, especially when those behaviors are composed together to develop an application design. Behavior composition could result in an unreliable design in which design faults are introduced due to unanticipated interactions between individual design components. These interactions must be analyzed using the behavior specifications of these components. It is important to note here that structural composition techniques could suffer even more from behavioral inconsistencies; hence, some designers prefer behavioral composition techniques. Whether a structural or a behavioral composition approach is used, careful behavioral analysis of the composed design is needed in order to detect such interaction violations. It is also important to note that several architectural design patterns are intended to produce designs in which interactions between objects are controlled by specific methods that tend to minimize coupling between objects and hence provide designs with controlled object interactions. The methodology that we present in this book is based on defining an interface for each design pattern instantiated. This interface provides a specification for the interactions between objects in different instantiated patterns and facilitates behavioral analysis.
The increasing numbers of patterns and pattern languages have prompted the development of classification techniques for patterns and pattern relationships. This goes back to the first book on patterns in which classifications of creational, structural, and behavioral patterns were introduced. A pattern map was also developed showing relationships between the 23 patterns described in Design Patterns: Elements of Object-Oriented Software [Gamma et al. 1995]. Since the text of each pattern could describe its relationship to other patterns, the pattern literature showed different classification schema of these relationships. James Noble developed a classification schema for the relationships between patterns [Noble 1998]. Three primary relationships and a number of secondary relationships between patterns are identified. The primary relationships are based on the following:
Uses relation, where a pattern uses another pattern
Refines relation, where a pattern refines another pattern
Conflicts relation, where a pattern conflicts with another pattern
Typically, more complex patterns use simpler patterns. For example, the Model-View-Controller (MVC) pattern [Buschmann et al. 1996] uses instances of the Observer pattern, the Strategy pattern, and the Composite pattern. The refines relationship can be used to define a new pattern as a refinement of a published pattern. The conflicts relationship shows that instances of these patterns should not be used together—that is, they provide mutually exclusive solutions to the same problem, and it is not possible to use them interchangeably. Noble also discusses a number of secondary relationships and shows how they could be defined using the primary relationships. This classification schema helps in the process of selecting patterns from pattern databases during the analysis phase of POAD, as we discuss later in Part III.
The main problem we discuss in this chapter is not how patterns relate to each other but rather how pattern instances can be composed together as building blocks to develop composite patterns, OO applications, or OO frameworks. This is related to the problem of composing software components at the design level. Understanding the relationships between individual patterns is a good practice but does not solve the issues related to pattern composition.
Behavioral Composition Techniques
Behavioral composition approaches are concerned with objects as elements that play multiple roles, where each role is part of a separate pattern. These approaches are also known in the OO literature as interaction-oriented or responsibility-driven composition [Wirfs-Brock & Wilkerson 1989]. Although the POAD composition approach, which is the main topic of this book, uses notation and composition techniques that are based on the pattern structure (i.e., its class model), we find it useful to be familiar with existing composition techniques that utilize the pattern's behavior model.
In this section we summarize behavioral approaches in modeling and composing patterns, and we analyze their advantages and drawbacks. Formalizing the behavior specification of individual patterns is important for the purpose of clarifying their semantics and facilitating their utilization by any pattern composition approach. We briefly summarize the work in the behavior specification field. We then discuss the approach presented by Trygve Reenskaug [Reenskaug 1996] on role modeling and synthesis using the OO role analysis method. We describe the work by Dirk Riehle [Riehle 1997] presented at the OOPSLA conference in 1997. This work applies the concepts of role models suggested by Reenskaug to pattern composition. Then, we briefly summarize the superimposition approach by Jan Bosch [Bosch 1998c], which uses design patterns and frameworks as architectural fragments and merges roles and components to produce applications. Finally, we discuss the role/type/class three-layer approach developed by Lauder and Kent [Lauder & Kent 1998], which takes a visual specification approach to describe design patterns.
Object-Oriented Role Analysis and Software Synthesis
The concepts of role models have emerged as a software analysis and design technique. Trygve Reenskaug developed the Object-Oriented Role Analysis and Software Synthesis (OORASS, later called OOram) [Reenskaug 1992; Reenskaug 1996]. Reenskaug presents a role model that abstracts the traditional object model and additionally recognizes a pattern of objects and describes it using a corresponding pattern of roles. While the notion of classes focuses on the capabilities of objects that are instantiated from these classes, the notion of roles focuses on the responsibilities of an object within the overall group of objects. According to Reenskaug, A role is an architectural representation of the objects occupying the corresponding positions in the object system.
The OOram method specifically addresses two development processes: the modeling process during which role models are created and the synthesis process during which several role models are composed together. In the following we will give an overview of the two processes.
A role model is a collaboration of objects that the analyst chooses to regard as a unit, separated from the rest of the application during some period of consideration or during analysis. In developing the system, the designer would then consider developing the whole application as a collection of role models, or so-called role synthesis [Anderson & Reenskaug 1992]. In role modeling, we suppress irrelevant objects, aspects, and details and generalize object identity. In role synthesis, new role models are derived such that every role is the composition of one or more roles from the base role models. The derived role combines several roles to be treated as a new single (composite) role.
During the modeling process, the real world is analyzed to identify a number of objects and their collaborations. This process is similar to many OO analysis methods in which we use requirements analysis techniques to identify objects, their types, and how they interact with each other. Based on objects' interactions, the role played by each object is identified. A role model is then created to separate different concerns. A role model describes the subject of a specific interaction, the relationship between objects (playing specific roles in the interaction), and the messages exchanged by objects. A single object can play multiple roles; each role played by the object belongs to a specific collaboration or role model. For instance, assume that we are building the enterprise information system for an organization. An employee in this organization may play multiple roles. He could be the traveler in the travel account model or a project manager in the payroll model. Each role model describes a limited aspect of the problem. Hence, when we do role modeling, we end up with several role models. The object plays different roles in those models.
In OOram roles are used as an abstraction to the traditional notion of classes and objects. Like a class, the role is a description of a set of objects. Whereas a class describes a set of objects with common behavior and characteristics, a role describes a synergy of several (not necessarily common) behaviors and characteristics. In addition, roles have properties similar to objects. In a role model, we can define the messages exchanged between roles and how they react to those messages.
The rationale behind using role models to capture both the static (class) and the dynamic (object) aspects of the model is that during modeling, the modeler would like to see the capabilities of the model elements (which are traditionally captured in class diagrams) as well as the behavior of the model elements and their message exchange (which are traditionally captured in object diagrams). OOram creators believe that while class diagrams are good at the implementation and development phase, they are not appropriate for the modeling phase, since they only capture the type information, while most message collaborations should be captured with those types as well. In the OOram modeling process, the analyst starts by analyzing the real-world system and identifying objects and their interactions. Then, the analyst abstracts those object interactions into role models. Figure 3-1 illustrates the modeling/abstraction process.
Figure 3-1. The OOram role modeling process.
The abstraction process considers focusing on individual collaborations at a time and discarding other collaborations in the object model. For instance, the analyst may drop objects of no interest (i.e., not relevant to a particular collaboration), suppress irrelevant aspects or details, or generalize the objects used in the collaboration. The result is a set of role models that are abstractions from the object diagrams. Objects in that sense become instances from the role and object collaborations become instances from role models.
The role model diagrams have constructs for modeling: a system role and its attributes, an environment role, a message path between collaborating roles, ports through which messages can be delivered to other roles, means to explicitly model a role's knowledge of another collaborating role, and means to model role and port multiplicity. An example of the role model view is illustrated in Figure 3-2. The example is taken from an airline booking system where several roles collaborate to make booking and payment.
Figure 3-2. An example of a role model for airline booking [Reenskaug 1996].
In addition to the role model views, OOram provides other views: a scenario view, which is similar to UML interaction or scenario diagram; an interface view, which is an enhanced role model that illustrates the exact messages over each collaboration arc; and method specification view, which is a scenario view with more details about the message processing inside the role.
The second process in the OOram method is the synthesis process. The synthesis process integrates various model views into one synthesized model view. The synthesis of base role models produces a synthesized (derived) role model. The synthesized role model consists of new roles and their collaborations. A composite role in the synthesized role model would then play various simple roles from other base role models.
The synthesis process combines role models (collaboration views), interface views, scenarios views, and method specification views. For the role model view, OOram specifies several rules for the synthesis process:
All roles from the base models should be mapped to roles in the final model.
Role attributes and collaborations should be mapped to attributes and collaborations in the final role model.
All ports in the base models should be mapped into corresponding ports in the final synthesized models.
The views capturing the synthesis process are complex even with few numbers of roles in each role model. Figure 3-3 illustrates the synthesis of two role models: one is the airline booking role model (top portion of the figure) and the other from the travel expense role model (bottom portion of the figure).
Figure 3-3. OOram synthesis of role models [Reenskaug 1996].
OOram finds role models appropriate for modeling design patterns where the pattern solution involves collaboration of interacting objects. A role base model could be created to represent the abstraction provided by the pattern. This pattern role model can be the source of synthesis. When patterns are modeled using a role model, the OOram synthesis procedure can then be used to compose patterns together. Composition of patterns using role models is not covered exhaustively as part of the OOram method. Dirk Riehle used role models to compose simple patterns and create composite design patterns, as discussed in the next section [Riehle 1997].
Role models used by Reenskaug for modeling design patterns differ from the archetypal object structures defined as design patterns in the pattern community. Reenskaug uses one model for both static and dynamic aspects of a role. Modeling a design pattern using roles includes both the static and dynamic aspects of the pattern in one diagram. The Unified Modeling Language (UML) avoids such integration. Static models are used for composition and interfaces purposes, while dynamic models are used for behavioral analysis and interaction purposes. Therefore, it is always better to keep separate models for each; however, maintain their relationship and consistencies.
In OOram, traceability of roles assigned to objects is a visual problem in the model synthesis. The diagrams of synthesized role models can be overwhelming with directed arrows synthesizing the roles from several role models. To overcome this problem, a tabular presentation of role models is suggested. Tabular representation is much less illustrative than visual models, such as collaborations, scenarios, or role models. Reenskaug shows how to document a framework using patterns described as role models (the MVC) similar to that described by Ralph Johnson in Documenting Frameworks Using Patterns (1992) with class diagrams.
Other work in behavior composition using roles can be found in Gottlob, Schrefl, and Rock's Extending Object-Oriented Systems with Roles (1996) and Kristensen and Osterbye's Roles: Conceptual Abstraction Theory and Practice Language Issues (1996). Kristensen and Osterbye focused on using roles in the context of conceptual modeling. In this context, roles are treated as first-class elements in the analysis of the application. A role represents a perspective on some objects as it emerges. Objects can be seen as role instances. Roles can be aggregated, specialized, and generalized. Their approach suffers from some limitations [Riehle 1996]. For example, how are roles attached to objects? How is the state of a role managed? How are state transitions of roles handled if they cross a specific role boundary?
Composing Design Patterns Using Roles
Role diagrams are developed to document the behavior of design patterns. Riehle (1997) uses role diagrams for composing design patterns. The role-based approach considers a pattern as a collaboration of roles. Objects and roles are treated slightly different. A role is a specific behavior defined as a result of collaboration and interaction with other roles. A design pattern captures a behavior that can be modeled using a set of interacting roles. Objects are usually used to refer to application specific objects. An object will play multiple roles in which each role defines the object behavior in a particular communication with other objects. To compose patterns, each pattern will be represented as a role diagram (a set or roles with some predefined relationships). The role diagrams are then composed to objects to develop an object diagram for the application.
Riehle investigated a particular composition problem related to what he calls composite design patterns. Composite design patterns are different from the GoF Composite pattern; whereas the Composite pattern is a design to capture hierarchy in OO applications, composite design patterns are composition of patterns whose integration shows a synergy that makes the composition more than just the sum of its parts. Thus a composite design pattern is simply a set of design patterns integrated and composed together so that the integration satisfies the condition of being a pattern; that is, it solves a recurring design problem.
To be more specific, Riehle discusses the process to create composite design patterns and does not address the general problem of composing any patterns. Though the problem is different, we can learn from the synergy and synthesis techniques used in developing composite design patterns. To create composite patterns, Riehle follows a simple procedure:
We model all existing patterns using role diagrams. Role diagrams are extensions to role models [Reenskaug 1996]. In addition, a set of composition constraints is defined in role diagrams. These constraints include the following:
An object playing a role (A) always plays another role (B) in the same collaboration, thus role A implies role B;
An object playing role (A) never plays role (B); and
Arbitrary mix two roles.
Role diagrams for several patterns, including Mediator, Observer, Chain of Responsibility, and Composite patterns, are illustrated in Dirk Riehle's Composite Design Patterns (1997).
The design uses a prototypical pattern application to derive the composite design pattern. A prototypical application is a concrete application, which is usually represented by an object diagram. It plays the role of the concrete example to be abstracted.
Using the collaboration diagram for the application, the designer starts to select the patterns that can be used in that application. He or she uses the role diagram of each pattern and assigns roles from the role diagram to objects in the application object diagram. At the end of this process, each object will be assigned several roles from several patterns.
Using the object diagram with the annotated roles, the designer creates a role relationship matrix. The role relationship matrix is used to analyze how roles are close to each other in terms of the composition constraints. The purpose of this analysis is to discover the pattern interaction synergy and unleash any composite roles. As a result, role equivalent sets that form composite roles are defined. The role relationship matrix is reduced to a final role relationship matrix.
The final role relationship matrix is used to create the role diagram for the composite design pattern.
In order to use this approach as a composition technique for design patterns in general, more work needs to be done in the way roles are assigned to objects. Moreover, we need to address the issue that we do not have domain objects (or application classes) to which we assign roles; instead, we want the role diagrams of individual patterns to derive the application-specific classes and objects.
The main drawbacks in modeling composite design patterns as compositions of role diagrams are identified as follows:
Choosing role diagrams as the primary means for describing pattern ignores class inheritance based patterns for which further techniques have to be developed [Riehle 1997]. The POAD approach works with patterns as structures of related classes and thus addresses the inheritance problem in role modeling.
Some patterns tend to be more complex when represented by role diagrams, specifically patterns with recursive structure like Composite or Chain of Responsibility patterns because …satisfying the boundary conditions increases the number of roles and composition constraints [Riehle 1997].
The process of composing role diagrams is an after-the-fact realization. …The creative process of working out the pattern did not proceed in the linear fashion as implied by the steps taken [Riehle 1997].
Role diagrams are not explicitly adopted by UML; the Object Constraint Language (OCL) and some extensions to collaboration diagrams can be used, however. One of the main obstacles confronting the adoption of OO approaches is the notation and semantics of modeling languages. UML finally brings OO modeling approaches together and unifies OO models. Therefore, new modeling approaches should be integrative with UML models.
There is no consensus on a unique role diagram for a design pattern. For instance, disagreement arises between the representation of Chain of Responsibility and Composite patterns [Riehle 1996; Reenskaug 1996]. Patterns are usually documented using class diagrams or object collaboration diagrams. Pattern authors do not use role models to document their patterns.
The main advantage of using a role diagram to model a pattern is that it provides a higher level of abstraction than class diagrams. A role model abstracts the main idea behind the pattern, while its class diagram provides implementation templates that could be several for one pattern (for example, several templates for the Observer pattern as compared to one role abstraction diagram [Riehle 1996]).
Architecture Fragments and Superimposition
The concepts of architectural fragments develop further from the concepts of role models. The approach by Jan Bosch in Specifying Frameworks and Design Patterns as Architecture Fragments (1998c) uses design patterns and frameworks as architectural fragments and merges roles and components to produce applications. Bosch introduces the notion of architectural fragments as a means to represent design patterns as reusable first-class entities. Fragments are used by Bosch to address the problem of underrepresentation of patterns in traditional OO languages, which provide no explicit support for representing a pattern as a first class element.
The approach is based on separation of behaviors. Each pattern captures a well-defined behavior. The behavior of a design pattern can be described as the behavior of a group of communicating objects. This behavior can be captured explicitly and regardless of the rest of the application functionality. An architecture fragment is used to capture this behavior. However, this behavior is not in isolation. Several of those behaviors need to be integrated and composed together. Therefore, a fragment composition approach is needed. Bosch introduces two techniques: first, a way to represent the behavior of a pattern (fragments), and second, a way to compose those behaviors (superimposition).
For a set of classes, each class plays a role in a specific behavior. An architectural fragment describes the parts of each of those classes that are specific for the specific behavior captured by the fragment. Hence, a fragment consists of a set of roles. A role is similar to a class. In addition, it defines a required interface. The required interface specifies the conditions that any class should meet if it is required to implement that role.
To compose patterns using the architecture fragment approach, each pattern is represented as a fragment. We may also have domain-specific design that contains classes (generated by the designer but not part of a particular pattern). Given a fragment representation for patterns and domain classes, a superimposition technique is used to compose those fragments together. Figure 3-4 illustrates the approach [Bosch 1998c]. This figure shows two fragments; the first has three roles and the second has two roles. It also has three domain classes. The result of the composition is a set of application classes that play several roles. For instance, an application class can play three roles: a role from fragment one, a role from fragment two, and a domain class.
Figure 3-4. Using fragments and superimposition to develop application classes.
In order to make this mechanism feasible, a specification language should be used to represent roles. A role could be as simple as a specification of interface operations or as complete as a specification for a whole class. In addition, a fragment representation has a way to specify what is required from any class that wishes to play a specific role that is part of that fragment. A layered object model (LayOM) [Bosch 1998b] language is used to specify roles and fragments.
As an example, consider the Observer pattern [Gamma et al. 1995]. This pattern as a fragment is composed of two roles: the subject and the observer. The subject role contains a set of objects, that is, observers that depend on the subject. It contains methods for subscribing and removing dependents. The observer role defines the interface to be called by the subject in case it changes. The Observer pattern representation using LayOM is illustrated in [Bosch 1998c]. One important observation that we make on the use of fragment roles to describe a pattern is that the class diagram of the pattern is not directly mapped to fragment roles. For instance, the Observer pattern class diagram has at least four classes with two abstract classes, representing the abstract subject and the abstract observer, and two or more concrete classes implementing the abstract classes and providing the application specific details. In the fragment modeling approach, the Observer pattern contains two fragment roles only.
A fragment consists of a number of roles. A role consists of variables, methods, acquaintances, layers, and interfaces as defined by the LayOM language. When designing the architecture, the designer selects a set of fragments to use and creates a set of domain-specific classes. The designer will have to choose which domain class will play the roles of those architecture fragments. The designer can also integrate fragments together by merging their roles. In order to do so, for a fragment-fragment composition, the role of one fragment has to be composed with the role of other fragments and for a fragment-component (class) composition, the role of a fragment has to be composed with the component's behavior. This composition would sometimes require overriding the component's behavior by the role to be superimposed on the component.
A superimposition mechanism is used by Bosch to compose roles and their behaviors. This superimposition mechanism is not supported by traditional languages. Bosch created a technique required for superimposition in the LayOM language. Through the use of layers, LayOM provides various techniques to compose behaviors.Due to the notion of superimposition and the LayOM layers that provide an implementation means, an advanced composition method is provided that facilitates the composition of roles and components. They provide the basic features necessary for specifying reusable architectural fragments that can be composed in powerful ways with suitable reusable components [Bosch 1998c].
The superimposition approach is developed to impose behavior on components using the fragment approach. It suffers from some limitation, especially when it comes to expressing OO techniques that are used in the class diagram level, such as inheritance. Since one of the important models used in documenting patterns is a class diagram, it is more likely that the fragment and superimposition approach will suffer from some limitation in implementing class model mechanism like subclassing.
This approach (as well as similar ones like aspect-oriented programming (AOP) [Kiczales et al. 1997]) suffers from a common drawback: they require the use of specific language to model the composition. For example, LayOM is required to model fragments and superimposition. In AOP, various aspects are described using a special aspect language that requires a specific compiler to weave aspects and merge the domain functionality with the aspect code. Hence, the designer has to learn an additional modeling language that is not part of UML (though some mapping and extensions could be made).
Bosch's approach uses the superimposition mechanism to allow a class to play several roles in architecture fragments. This approach is targeted for lower level implementation of patterns and frameworks and assumes the existence of application-specific classes to which we want to assign a role (i.e., merging process). It is a behavior composition approach of roles. The POAD approach is a structural composition approach that uses pattern views that comply with UML models, which we believe could be simpler (and more familiar) for the designer to use.
Visual modeling is one of the important aspects of modern software development. Lauder and Kent (1998) take a visual specification approach to design patterns. The perception is that capturing a design pattern using the current documentation templates, such as a class diagram or a sequence diagram, is not sufficient for abstracting what the pattern is about. This is usually because the pattern's textual representation and models capture domain-specific information that should be removed from an abstract representation. For instance, in the Observer pattern [Gamma et al. 1995] the sequence diagram model shows two concrete observers for one concrete subject. Similarly, the class diagram for the Abstract Factory pattern shows two concrete factories and two concrete products named ConcreteProductA1 and ConcreteProductA2. These concrete examples hinder the abstract representation of patterns and hence their composition. Composition should be done at a higher level where details such as the cardinality as well as the concrete names are abstracted and patterns are represented in a way that captures only their essential spirit. Moreover, this abstraction should be supported by graphical notation to enable tool support and facilitate deployment.
Lauder and Kent attribute the reason for these application details that appear in the class diagrams to the absence of a way to express and reason about collections without having to explicitly mention names for the members of collections and their cardinality. As a solution, Lauder and Kent utilize constraint diagrams developed by Kent (1997) together with UML diagrams to represent constrained sets and set members that constitute a notation to describe arbitrary collections.
Using UML and constraint diagram, the UML representation of class is amended with a fourth compartment (other than the traditional UML name, attribute, and operations compartments) to represent abstract instances of that class. The fourth compartment will not carry textual representation of the instances; however, it will carry constraint diagrams that describe instances as sets and relationships between sets using constraint diagram notation. An example is shown in Figure 3-5.
Figure 3-5. A UML class diagram with abstract instances compartment.
In order to purify design patterns from textual details that cause ambiguity, three model representations or layers of abstractions are used to describe a pattern. The three layers are role-model layer, type-model layer, and class-model layer. Role-model is an abstraction of type-model, which in turn is an abstraction of the class-model. The top layer does not use any domain-specific names in describing the pattern; it uses a constraint diagram and extensions to UML models to describe the semantics of the operations rather than their names. It represents the pattern in terms of highly abstract state and highly abstract semantics using a constraint set that captures the essential spirit of the pattern. The type-model is then a refinement of the role-model by adding some specific refinements to the abstract state and semantics such as adding the name of the operations and their concrete syntax. Finally, the class-model is the refinement of the type-model to add application-specific terms such as concrete classes, concrete state names, and concrete method implementations. As an example, the abstract factory is represented in this three-layered abstraction as illustrated in Figure 3-6 [Lauder & Kent 1998].
Figure 3-6. .Abstract factory role-model, type-model, and class-model.
This approach is more concerned with visualizing individual patterns than composing patterns. The three-layer abstraction concept is useful in purifying the pattern from application-specific and domain-specific naming and cardinality, especially when dealing with collections. The composition of design patterns using abstract representation of the pattern could be more useful, since we may abstract concrete names—for example, ConcreteProductA. However, the three-layers modeling technique seems visually complex even for simple patterns such as Observer patterns (figures shown in Lauder et al. 1998). Modeling the dynamics of patterns by abstracting the sequence diagram specification and adding the constraint diagram notation could be very complex for most behavior patterns and hence may make the approach difficult to use for pattern composition. Although this work is more concerned with visualizing individual patterns than composing patterns, it can be extended to define composite patterns based on static and dynamic specifications. Applying the approach to composition of patterns is still an immature research area.