Programming with Patterns Peter Forbrig Universit¨at Rostock
[email protected]
Ralf L¨ammel CWI Amsterdam
[email protected]
Abstract Language support for object-oriented programming with patterns is provided. Thereby, designs making use of design patterns can be implemented in a more direct and traceable way. The essential language constructs are nested classes and a kind of superposition for class structures. A corresponding experimental programming language PaL is discussed. The current implementation is based on a compilation to Eiffel. A library covering the 23 GoF patterns has been developed in PaL. To be able to capture reuse schemes for patterns, a new form of abstraction is introduced. A non-trivial drawing application has been developed as a case study for programming with patterns.
1 Introduction Design patterns [6] are widely used in object-oriented design. They are an accepted means of documenting and communicating software design experience. A pattern describes a solution to a recurring design problem in a systematic and general way. The application of design patterns in programming, however, is usually based on manual implementation in an ordinary object-oriented programming language resulting in problems like lack of encapsulation, traceability and reusability of the patterns. We present an extension of the object-oriented programming model providing a good integration of programming and design patterns. The model can be complemented by corresponding language support as illustrated with the experimental language PaL. The expressive power is gained by supporting a certain kind of superposition for nested class structures. Superposition surpasses standard methods of reuse such as inheritance, genericity and composition. Let us point out some important reasons why it is not easy to support design patterns at the level of programming, First, certain ingredients of pattern descriptions are usually presented in an informal language, e.g., the discussion of applicability issues and trade-offs. Second, the common programming model does not support a reusable first-class representation of the implementable structure of the patterns. This paper addresses the second issue by supporting reuse for the class diagrams resp. class structures underlying design patterns. The paper does not attempt to translate design patterns resp. designs based on them completely into ‘programming patterns’ resp. programs. This is not sensible, since design patterns are real design artifacts. The paper is structured as follows. In Section 2, an extended object-oriented programming model is developed. In Section 3, language constructs supporting the new programming model are described, and a corresponding language PaL is outlined. A pattern library covering the GoF [6] pattern catalogue is outlined in Section 4. A case study for a non-trivial drawing application DrawIt is discussed in Section 5. As a result of our experiences, a new kind of abstraction to capture reuse schemes for patterns is developed in Section 6. Finally, in Section 7, the paper is concluded. 1
2 Programming model For brevity, only an overview on the pattern-oriented programming model is given. We emphasize the requirements to support design patterns at the programming level. The new model provides solutions for problems one encounters when patterns are implemented in a traditional object-oriented language. These problems were also considered by Bosch in [2]. It is obvious that the new model should be a conservative extension of the basic object-oriented model, i.e. concepts such as information hiding, encapsulation, inheritance and polymorphism should be preserved. Patterns as first class citizens In the traditional programming model, patterns have to be coded as conglomerations of classes resulting in a lack of traceability and encapsulation of the patterns. Patterns should be traceable in the source text so that a reader can easily identify them. In [8], Jacobsen argues that patterns are abstractions over programs. The new programming model provides a corresponding kind of abstraction in order to protect the structure underlying a pattern as a whole. A pattern is regarded as a nested class encapsulating the participating classes.
Figure 1. Class structures and superposition: Visitor & Composite Example 1 The left part of Figure 1 illustrates the class structure1 PVISITOR underlying the well-known Visitor pattern. We will clarify later on why the chosen structure is more abstract than in the GoF pattern catalogue [6]. PVISITOR encapsulates the participating classes ELEMENT, CONCRETE ELEMENT, VISITOR, CONCRETE VISITOR with the fundamental methods accept and visit concrete element. Some of the slots for attributes and methods in the figure are empty. Reusability of class structures Reuse of object-oriented programs is based on concepts like inheritance, composition, genericity and interfaces. However, these concepts are not sufficient to implement patterns in a reusable way. One is forced to code the solution provided by a pattern in each specific application context. 1 To illustrate class structures, a graphical UML-like notation is used, which is not meant to describe all details of the illustrated class structure. The method bodies will not be shown, for example. Note that a method is not just listed in the class introducing the method, but also in all subclasses. Later on, proper language support will be provided.
Example 2 Let A and B be classes, where B inherits from A. If these classes need to be refined for actual reuse, two classes A0 and B 0 can be defined such that A0 inherits from A and B 0 inherits from B . Unfortunately, it does not hold that B 0 inherits from A0 . This simple example illustrates the limitations of inheritance to facilitate reuse of class structures because the original inheritance relation is not preserved by the refined class system. Among other problems, polymorphism will not be supported accordingly in the refined class system. Generic classes [11] do also not provide a general solution for refinements of class structures, e.g., the duplication of classes participating in a pattern cannot be modelled. We suggest superposition operating on class structures as another form of reuse. It is a form of “grey-box” reuse rather than white-box reuse as is inheritance which operates at the class level. As for the graphical notation used in the paper (refer to Figure 1), superposition will be visualized by arrows. Entities (i.e. class structures, participating classes, and methods) are connected, where the arrows go from the superimposed class structures to the resulting class structure. The arrows are only given if renaming is involved. If names of classes or methods remain the same, the arrows are omitted. Note that the graphical notation does not emphasize what parts of the resulting structure are not provided by the reused class structures. In contrast, the graphical notation emphasizes the resulting structure and it indicates the reused parts. Example 3 The right part of Figure 1 illustrates the class structure underlying the Composite pattern and its derivation by superposition. First, let us focus on the final class structure PCOMPOSITE which is a slight abstraction of the corresponding GoF variant. There is an abstract superclass COMPONENT with a method operation, and the subclasses LEAF and COMPOSITE. The latter class enforces an interface to add and remove components. COMPONENT declares an abstract method operation. The abstract class PARAMETER models eventual parameters for operation. Now let us focus on the superposition. The class structure PCOMPOSITE was derived from the class structures (say auxiliary patterns) PCONTAINER modelling a minimal interface for container functionality and PPARAMETER concerned with the idiom for methods with abstract parameters. Note that PCOMPOSITE adds structure to the reused class structures. LEAF was not present at all in PCONTAINER. Also, the inheritance relation is established as required for composites. Finally, note that classes enclosed by PCONTAINER are renamed in PCOMPOSITE.
Combination of patterns Patterns can be related in various ways [13]. At the programming level, the relationship saying whether certain patterns can or need to be combined is important. The combination (or composition) of patterns is an even more intricate problem than the reusable implementation of patterns. In the suggested model, combination is made possible by certain key features of superposition facilitating reuse of class structures. It is possible, for example, to unify or duplicate classes in a class structure and to merge different class structures. It is impossible to perform such adaptations in terms of genericity and inheritance without breaking the class structure. Example 4 An interesting combination of patterns concerns the Composite and the Visitor patterns. The Composite pattern is a structural pattern, whereas the Visitor pattern is a behavioural pattern. The former pattern can be extended by the behaviour according to the latter pattern in a sensible way. The subclasses of COMPONENT can be regarded as classes for concrete elements in the sense of the Visitor pattern. Thus, the visitor is refined to support one visit-method for each subclass of COMPONENT. Dually, COMPONENT and its subclasses inherit the accept-method from CONCRETE ELEMENT. This combination can be expressed by superposition of PCOMPOSITE and two instances of PVISITOR. In one instance of PVISITOR, the class CONCRETE ELEMENT is renamed to LEAF, whereas in the other instance of PVISITOR, the class CONCRETE ELEMENT is renamed to COMPOSITE. In both instances, the class ELEMENT is renamed to COMPONENT.
3 Language support Programming with patterns can be supported by suitable language constructs. This will be illustrated with the experimental language PaL (Pattern Language) whose implementation model is currently based on a compilation to Eiffel. A version of PaL has been used to develop the pattern library and to perform the case study described in subsequent sections. Let us point out the required language constructs as illustrated in Figure 2. The first crucial construct corresponds to nested classes. The second important concept is superposition facilitating reuse of nested class structures. class cid [inherit cid 1 ; : : :; cid m ] [reuse ref 1 ; : : :; ref n ] [creation mid 1 , : : :, mid q ]
c1 : : : ck f1 : : : fl
end -- cid
-- common inheritance -- see right column -- methods for initialisation -- nested (encapsulated) classes -- features (methods + attributes)
cid 0 [rename qname 1 as name 1 ,
: : :,
qname p as name p ]
Figure 2. PaL—syntax (nested classes & references for reuse) Nested classes The solution underlying a pattern contains a class structure which can be conceived as a nested class. In Figure 2, the only additions to the common (Eiffel) class syntax are the encapsulated class descriptions c1 , : : :, ck , and the optional reuse-clause. For the moment being, let us focus on classes without reuse-clause, say basic classes. class PVISITOR class ELEMENT feature accept(a visitor: VISITOR) is deferred end end -- ELEMENT class CONCRETE ELEMENT inherit ELEMENT feature accept(a visitor: VISITOR) is do a visitor.visit concrete element(current) end end -- CONCRETE ELEMENT class VISITOR feature visit concrete element(an element: CONCRETE ELEMENT) is deferred end end -- VISITOR class CONCRETE VISITOR inherit VISITOR end end -- PVISITOR
Figure 3. The Visitor pattern (PaL) Example 5 Figure 3 contains the PaL program for the PVISITOR pattern. There are four participating classes, ELEMENT, CONCRETE ELEMENT, VISITOR and CONCRETE VISITOR, where the ‘concrete’ classes inherit from the ‘abstract’ classes. In the class ELEMENT, the common accept method is declared to be abstract. For CONCRETE ELEMENT, an implementation is provided for accept by invoking the corresponding visit-method of the accepted visitor.
Superposition vs. inheritance Classes with a reuse-clause (refer to Figure 2) are called derived classes. The referenced (possibly nested) classes ref 1 , : : :, ref n , and the (non-nested) class structure consisting of the classes c1 , : : :, ck plus the features f1 , : : :, fl are superimposed. Superposition
class PCONTAINER
class PPARAMETER
class CONTAINER feature add(an item: ITEM) is deferred end -- add feature remove(an item: ITEM) is deferred end -- remove end -- CONTAINER
class COMPONENT feature operation(a parameter: PARAMETER) is deferred end -- operation end -- COMPONENT
class ITEM end
end -- PPARAMETER
class PARAMETER end
end -- PCONTAINER
Figure 4. Container functionality and abstract method parameters means to merge classes of the same name. The inheritance relation for classes of the reused structures is preserved. Referring to class structures, structural components can be renamed. The syntax of a reference ref i to a class cid 0 is given in the right column of Figure 2. Classes and features qname 1 ; : : : ; qname p contained by cid 0 can be renamed. Since we are concerned with nested class structures, the entities to be renamed can be at some level of nesting. Thus, the qname j are qualified names based on a common “.”-notation. The new names do not need to be qualified, since renaming should not change the level of an entity. class PCOMPOSITE reuse PCONTAINER rename CONTAINER as COMPOSITE, ITEM as COMPONENT; PPARAMETER class COMPOSITE inherit COMPONENT end -- COMPOSITE class LEAF inherit COMPONENT end -- LEAF end -- PCOMPOSITE
class AbstractCompositeVisitor reuse PCOMPOSITE; PVISITOR rename ELEMENT as COMPONENT, CONCRETE ELEMENT as LEAF, VISITOR.visit concrete element as visit leaf; PVISITOR rename ELEMENT as COMPONENT, CONCRETE ELEMENT as COMPOSITE, VISITOR.visit concrete element as visit composite
Figure 5. Derivation of Composite and combination with Visitor Example 6 The PaL implementation of the Composite pattern is shown in Figure 5. The basic classes PCONTAINER and PPARAMETER, whose PaL implementation is shown in Figure 4, are reused. These class structures are superimposed (after some renaming) with the ‘new’ classes COMPONENT, LEAF and COMPOSITE. Due to renaming and merging, the classes COMPONENT and COMPOSITE are unified with the container classes ITEM and CONTAINER. Multiple reuse It might never be agreed upon if multiple inheritance is strictly needed. For the new reuse-construct facilitating superposition, the situation is clear. Multiple reuse is strictly needed, since intentionally several class structures should be superimposed. Multiple reuse has been used even for the simple example above deriving the Composite pattern. Conceptually, multiple reuse is needed if patterns need to be combined, or if a pattern is needed in several variants to be merged. The latter scenario often occurs during refinement of patterns, since parts of a class structure need to be duplicated. This is an interesting aspect. For inheritance, the scenario with multiple inheritance from one class is rather problematic, whereas multiple reuse of the very same class is one of the intended applications of superposition.
Example 7 The combination of the Composite and the Visitor patterns as discussed in Example 4, can
be described in PaL as shown in the right column of Figure 5. PCOMPOSITE is reused once, whereas PVISITOR needs to be reused twice, since there are two candidate classes for CONCRETE ELEMENT, namely LEAF and COMPOSITE. The class ELEMENT from the Visitor pattern is unified with the class COMPONENT from the Composite pattern in both cases. For completeness, a meaningful extension is indicated. The derived class structure AbstractCompositeVisitor could be easily extended with iterator functionality. The accept-method of COMPOSITE should be worked out then to broadcast a visitor to all the aggregated components.
Name clashes While merging the (possibly renamed) class structures to be superimposed, some name clash resolution for features might be required. Name clashes might occur, for example, when classes from different class structures with the same name and level are unified. For brevity, we omit the discussion of constructs to resolve name clashes explicitely. They are rarely needed anyway. In the sequel, we only consider ‘automatic’ name clash resolution. A name clash is certainly unresolvable if features of different kinds (attribute vs. method) or types are involved. On the other hand, it is perfectly acceptable if two abstract methods with the same type clash. Conflicts might also be resolved based on priorities associated with the class structures resp. their components. In this paper, the simple rule is applied that the classes c1 , : : :, ck and the features f1 , : : :, fl (refer to Figure 2) have an higher priority than those from ref 1 , : : :, ref n . There is a similar well-established rule for inheritance, that is to say a method implementation is meant to override inherited implementations for the same method. Example 8 There is only one innocent name clash involved in the derivation of the Composite pattern, that is to say the unified copies of ELEMENT both provide the abstract method accept. Since the methods are abstract and the types do not differ, this clash can be resolved automatically.
Semantics & Implementation In [12], Seemann develops a denotational semantics for basic nested class structures. Operations on structures, as modelled, for example, by the reuse-construct of PaL, are studied according to a model-theoretical approach. On the other hand, derived class structures can also be transformed into basic ones by a translation unfolding reuse-constructs in the sense of a copy-style semantics. In [5], B¨unnig completes such a transformation-oriented view on superposition by a compilation model with Eiffel as the target language. Patterns with the participating classes are compiled to Eiffel classes using a model, which supports polymorphism at the pattern level, and which maps the reuse relationship between patterns to inheritance. The formal specification framework [7] was used for the implementation.
4 A library of reusable patterns A library of reusable patterns developed in PaL is outlined. The library has been used for the case study described in the subsequent section. Essentially, the library offers variants of the 23 patterns in the GoF catalogue [6]. Besides, certain auxiliary patterns and combinations are provided. The variants in the library need to deviate from the catalogue-like representation in [6] because the PaL patterns are meant to support reuse of the corresponding class structures in actual programming. In contrast, the catalogue [6] serves rather as a reference and as an illustration. Similar patterns The GoF catalogue is structured in a way that creational, structural and behavioural patterns are separated. Numerous other classifications and relationships for patterns have
been studied. Interestingly, an executable pattern library may make explicit the reuse relationship between patterns, that is to say how one pattern can be derived from other patterns. For example, the Abstract Factory pattern can be derived from the Factory Method pattern. Such conceptual similarities can be made explicit in a language like PaL. Thus, patterns and reuse are taken serious in the design of the library itself. Fundamental class structures Patterns as described in the GoF catalogue tend to illustrate the opportunities for reuse. Therefore, they sometimes are not minimal in the sense that the provided structures are usually just one special refinement of a more fundamental structure. This is perfectly right for a non-executable pattern catalogue, but it would mean overspecification and hindrance of reuse for a pattern library. Superposition is sufficient to derive specific structures starting from the fundamental one, e.g., by duplicating certain participating classes. The Abstract Factory pattern, for example, is explained in [6] with two concrete factories which can generate two products. Conceptually, any number of conrete factories and products is sensible. Thus, our library assumes a fundamental structure with just one concrete factory and one product. Abstract methods and parameters Another crucial difference between our library patterns and the GoF catalogue is the way how behaviour and method parameters are handled. Conceptually, behaviour is described by method implementations. Interaction between classes means method invocation and parameter passing. The actual behaviour and the parameters needed for a certain method often depend on the actual instantiation of a pattern. The GoF catalogue tends to enforce a pseudo-code implementation or one common implementation and empty parameter lists in these cases. For the library, a different treatment is sensible. If there is no non-plus-ultra implementation for a method, it is kept abstract. Potential parameterized methods are declared with an abstract parameter class. If multiple parameters are needed, they will be modelled as attributes of this parameter class.2 The auxiliary pattern PPARAMETER presented earlier in the paper (see Figure 4) provides the corresponding idiom for methods with abstracts parameters. Example 9 The GoF catalogue of the Composite pattern does not indicate parameters for operation, since parameters are application-dependent. For the class COMPOSITE, a (pseudo-code) implementation for operation is indicated, where operation is invoked for all aggregated components. The version of our library pattern Composite pattern shown in Figure 5 is more abstract. It is derived from a general auxiliary pattern providing container functionality and from the auxiliary pattern for methods with abstract parameters. The resulting class structure differs from the catalogue variant in the sense that operation has one parameter and that no implementation for operation is provided in the class COMPOSITE.
5 Case study The case study described in the sequel is concerned with a non-trivial drawing application DrawIt implemented in PaL. It is demonstrated how patterns can be used at both the design and the programming level. The case study demonstrates the applicability of the methodology and the developed tools. DrawIt is complex enough to demonstrate several non-trivial design problems. Several parts of the presented design are reusable for similar applications because of the stepwise manner how specific application patterns resp. class structures are derived from abstract library patterns. 2 This solution is certainly debatable. One might argue that the introduction of method parameters should be supported directly by the language. This is a topic for future work.
Requirements The graphical editor has to satisfy the following requirements: 1. Creation of graphical elements like lines, rectangles and ellipses in a vector-oriented way 2. Manipulations of graphical elements like move, delete, group, ungroup, connect and so on 3. Undo and Redo 4. Intuitive user interface Design issues
We indicate some relevant design problems:
1. How to represent graphical elements internally so that extensibility is supported? 2. How to implement a highlighter? Graphical elements are highlighted within editors to select a focus for subsequent actions. Highlighting should be incorporated in a smooth way. 3. How to implement connectors? Using graphical editors, graphical elements have often to be connected / grouped. The concept should also be added in a modular way. 4. How to implement commands in order to support Undo and Redo? Patterns play an important role in addressing these design problems.
Figure 6. The functionality of the Graphic Composite
Programming with patterns The program DrawIt manipulates documents which contain graphical elements like lines, rectangles, ellipses, and pictures (i.e. compound elements). We assume a superclass GRAPHIC which defines attributes like position and extension required for all elements. Special attributes of ellipses, rectangles etc. are defined by the corresponding classes. A picture is a composition of graphical elements. It is clear that the structure exposed by graphical elements can be captured with the Composite pattern. The method move and resize is declared to be abstract in the class GRAPHIC. For the class PICTURE, it can already be specified that the method delegates a message to all aggregated objects. Additional functionality like drawing has to be added to a structure of graphical elements. This has to be done in such a way that readability, reuse and extensibility are supported. In Section 2, the combination of the Composite and Visitor pattern was discussed. Exactly this idea is applicable to the structure of graphical elements and a concrete visitor, which supports the drawing of elements. The combination of both patterns results in the class structure in Figure 6. A concrete visitor inherits the interface from the abstract visitor and implements the functionality of visiting an ellipse, a rectangle, a line or a picture. Each class of the structure of GRAPHIC has a method accept(VISITOR v), which activates the correct functionality of the visitor belonging to the current element. In this way, the whole functionality is encapsulated within the concrete visitor. Let us shortly address the remaining design decisions. The Decorator pattern is applicable to design and to implement highlighting. The Decorator pattern can also be used in combination with the Observer pattern for the purpose of connectors. Operation of the users can be implemented by the use of Command pattern. In this way, the application DrawIt was developed (and not just designed) by combining patterns.
6 Reuse schemes Experiences with pattern-oriented programming show that there is an expressiveness problem with the language support as it stands now. Class structures can be reused but the programmer is not supported in finding the right scheme of reuse. These schemes cannot be represented as class structures. Conceptually, such schemes are abstractions on class structures similar to mixins in ordinary object-oriented programming. We discuss a corresponding concept of reuse schemes in the sequel. Example 10 To illustrate the problem with schemes of reuse, let us consider the Composite pattern. An important step in an instantiation of this pattern is concerned with enforcing several kinds of leaf components. The approach dictated by our programming model and PaL is to perform multiple reuse of the abstract Composite pattern, where the class LEAF is renamed in each copy differently. It is clear that this approach using multiple reuse for the duplication of the LEAF class is tedious for a programmer. The intended mode of reuse is not represented by the description of the Composite pattern in Figure 5 itself, but it has rather to be observed and implemented by the user over and over again. Schemes as abstractions A pragmatic approach to solve this kind of problem would be to suggest a kind of macro facility. We rather would like to provide proper language support for reuse schemes. By this, the schemes become first-order citizens of the language and they are subject to full static type checking. The actual concept described in the sequel has been inspired by the fragment system of the Mjølner BETA system [9], and by certain meta-programming concepts. We propose a new
kind of abstraction for so-called (reuse) schemes. In simple terms, schemes are ‘schematic’ reuseclauses with place holders involved. Consequently, schemes can be used in the development of a class structure in the reuse-part almost like an ordinary reuse-clause. In using a scheme, a simple where-notation has to be used to replace the place holders of the referenced scheme by actual names of classes or features. scheme DeriveComposite PCOMPOSITE rename COMPONENT as hcomponenti, LEAF as hleaf1 i, COMPOSITE as hcompositei; ...; PCOMPOSITE rename COMPONENT as hcomponenti, LEAF as hleafn i, COMPOSITE as hcompositei end -- DeriveComposite
class AbstractGraphicComposite reuse DeriveComposite where hcomponenti = GRAPHIC, hleaf1 i = ELLIPSE, hleaf2 i = RECTANGLE, hleaf3 i = LINE, hcompositei = PICTURE end -- AbstractGraphicComposite
Figure 7. The scheme of reuse for the Composite pattern Example 11 In Figure 7, a scheme for reusing the Composite pattern is defined (see left column) and used (see right column). The scheme was motivated in Example 10. It is used here for the derivation of a class structure AbstractGraphicComposite useful in the discussed case study. hcomponenti, hleaf1 i, : : :, hleafn i, hcompositei are place holders for actual classes to replace the classes COMPONENT, LEAF and COMPOSITE in the various copies. Such place holders can be regarded as formal parameters of a reuse scheme. Using the scheme means to supply actual parameters, that is GRAPHIC, ELLIPSE, RECTANGLE, LINE and PICTURE as facilitated by the where-notation. The resulting class structure does not yet contain any functionality. The actual Graphic Composite required in the case study can be derived by further refinements. The way how we deal with place holders, especially list-typed place holders (in Figure 7: the hleafi i) is a bit pragmatic. A suitable notation still needs to be identified. Conceptually, we might need type declarations for place holders, and a construct to iterate over the actual values supplied for list-typed place holders as indicated (in a pragmatic way) in the definition of the scheme DeriveComposite (by using indices and ... notation). More research is required in this respect. Static checks Let us explain how such schemes can be statically checked. It is clear that this property is invaluable for a library not only containing class structures encoding certain patterns, but also reuse schemes. All non-place holder names (in Figure 7: PCOMPOSITE, COMPONENT, LEAF, COMPOSITE) can be checked to be associated with the corresponding kinds of abstraction statically. Due to the static semantics of the constructs involved in the scheme, there has to be a class PCOMPOSITE with nested classes COMPONENT, LEAF and COMPOSITE. Trying to rename some classes which are not contained in PCOMPOSITE causes an error solely at the time of checking the scheme. Thus, the scheme provides a proper abstraction on its own. In a more technical sense, the scheme can actually be checked like a reuse-part of an ordinary class assuming some fresh and pairwise distinct class names for hleaf1 i, : : :, hleafn i. Such assumptions can be modelled via a simple constraint system. If by accident, at scheme instantiation time, a class name causing a name clash is supplied for some leafi , namely COMPONENT or COMPOSITE, instantiation fails. Such a type error can be traced back to the scheme as well.
Combination of patterns Reuse schemes are also convenient for more complex scenarios than the one outlined for the Composite pattern. Actually, reuse schemes can capture the underlying scheme of superposition for the combination of different patterns. Here, the status of schemes as first-class citizens turns out to be essential. In describing the scheme of combination for some patterns, we can refer to the schemes for reusing these patterns. scheme DeriveVisitor PVISITOR rename ELEMENT as habstracti, CONCRETE ELEMENT as hconcrete1 i, VISITOR.visit concrete element as hvisit1 i; ...; PVISITOR rename ELEMENT as habstracti, CONCRETE ELEMENT as hconcretem i, VISITOR.visit concrete element as hvisitm i end -- DeriveVisitor
scheme DeriveCompositeVisitor DeriveComposite; DeriveVisitor where habstracti = hcomponenti, hconcrete1 i = hleaf1 i, ..., hconcretem?1 i = hleafn i, hconcretem i = hcompositei end -- DeriveCompositeVisitor
Figure 8. Schemes of reuse: Vistor pattern & combination with Composite pattern Example 12 In Figure 8, the scheme DeriveCompositeVisitor modelling the combination of the Composite and the Visitor pattern is defined (see right column). It refers to the reuse schemes DeriveComposite (see Figure 7) and DeriveVisitor (see left column in Figure 8). Thus, a certain kind of combination of the Composite and the Visitor patterns is expressed in terms of given reuse schemes for these patterns. We should shortly explain the reuse scheme DeriveVisitor for the Visitor pattern. It captures the idea that a visitor separates several kinds of concrete elements according to some object structure, and supports a visit-method for each kind of concrete element. Again, this scheme takes the form of a reuse-part of a class description. The scheme DeriveCompositeVisitor captures the general idea of composing some object structure adhering to the Composite pattern structure with Visitor pattern behaviour. The scheme composes two reuse-parts according to the schemes DeriveComposite and DeriveVisitor. It unifies certain place holders of the two referenced schemes.
7 Concluding remarks Results We have developed an extension of the object-oriented programming model which provides traceability, reusability for first-class representations of class structures underlying design patterns. The language PaL implements this model. Patterns are modelled as nested classes. It is a major contribution of the paper to indicate how reuse (say instantiation, refinement and combination) of patterns can be modelled based on superposition of class structures, and how schemes of reuse can be captured by a new kind of abstraction. The pattern catalogue of [6] was implemented in PaL. We presented a case study to implement a non-trivial drawing application taking advantage of the completion of the object-oriented programming paradigm by patterns. Related Work Suitable macro mechanisms (e.g., [10]) or program transformation / meta-programming frameworks are expressive enough to attempt a reusable implementation of design patterns. The contribution of our work is to model reuse of patterns as proper language construct, and to provide a corresponding generalization of the object-oriented programming model. Design patterns can be represented in Bosch’s Layered Object Model (LayOM) [1, 2]. C++ classes can be derived from the LayOM representation. [2] indicates how patterns can be combined
in the model. However, patterns are not supported directly by LayOM. In [4], a forms-based tool that automates the implementation of design patterns is described. From application-specific information, e.g., the names for the participants, the tool generates the pattern-prescribed code (C++ classes). The paper does not attempt to provide a general approach to the specification, application and combination of design patterns. There are several related tools (case tools and others) which attempt to support programming with patterns. They usually lack a clear programming model. In [13], Zimmer regards design patterns as operators to evolve designs by transformations. Most other authors (including us) rather focus on the solution provided by a pattern. However, Zimmer’s approach addresses design and not programming. The description language he develops is not completely operational because he copes with issues which require user intervention. Future work Finally, let us indicate a few directions for future work. An interesting goal is to cover further elements of common pattern descriptions such as applicability issues, trade-offs, or behavioural aspects. Also, the concept of reuse schemes definitely requires further research. The concept should be worked out so that it can be integrated with a language like PaL. We are working on tool support for programming with patterns.
Acknowledgement The authors are indebted to Stefan B¨unnig and Normen Seemann. This paper relies on their work done in the context of their student research projects and masther’s theses [5, 12]. Ralf L¨ammel was supported, in part, by NWO, in the project Generation of Program Transformation Systems.
References [1] J. Bosch. Design Patterns & Frameworks: On the Issue of Language Support. In Bosch et al. [3]. [2] J. Bosch. Design patterns as language constructs. Journal of Object-Oriented Programming, 11(2):18–32, May 1998. [3] J. Bosch, G. Hedin, and K. Koskomies, editors. Proc. LSDF’97, 1997. [4] F. J. Budinsky, M. A. Finnie, J. M. Vlissides, and P. S. Yu. Automatic code generation from design patterns. IBM Systems Journal, 35(2), 1996. ¨ [5] S. B¨unnig. Entwicklung einer Sprache zur Unterst¨ utzung von Design Patterns und Implementierung eines zugeh¨origen Compilers. Master’s thesis, University of Rostock, Department of Computer Science, July 1999. [6] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, October 1994.
[7] J. Harm, R. L¨ammel, and G. Riedewald. The Language Development Laboratory ( ). In M. Haveraaen and O. Owe, editors, Selected papers, NWPT’96, Research Report 248, pages 77–86, May 1997. [8] E.E. Jacobsen. Design Patterns as Program Extracts. In Bosch et al. [3]. [9] J. L. Knudsen, O. L. Madsen, C. Nørgaard, L. B. Petersen, and E. Sandvad. An overview of the Mjølner BETA system. Technical report, Mjølner Informatics ApS, March 1990. [10] S. Krishnamurthi, Y.-D. Erlich, and M. Felleisen. Expressing Structural Properties as Language Constructs. In S. D. Swierstra, editor, Proc. ESOP’99, volume 1576 of LNCS, pages 258–272. Springer-Verlag, 1999. [11] J. Palsberg and M. I. Schwartzbach. Type subsitution for object-oriented programming. SIGPLAN Notices, 25(10), October 1990. Proc. OOPSLA / ECOOP’90. [12] N. Seemann. A Design Pattern Oriented Programming Environment. Master’s thesis, University of Rostock, Department of Computer Science, July 1999. [13] W. Zimmer. Frameworks und Entwurfsmuster. PhD thesis, Universit¨at Karlsruhe, 1997.