Towards tool support for design patterns using program ... - LIP6

10 downloads 540 Views 109KB Size Report
described as program transformations and how an assistant tool could ..... hand, Creator does not necessarily correspond to the class that needs to create.
Towards tool support for design patterns using program transformations *,**

Mikal Ziane *

Laboratoire d'Informatique de l'Université Paris 6 Pole IA 8, rue du Capitaine Scott 75015 Paris, France [email protected] **

Université René Descartes (Paris 5) Paris, France

ABSTRACT. Design patterns have greatly helped spreading a limited number of well-tried solutions to recurring object-oriented problems. But as new patterns are introduced at a steady rate, the concept must evolve so that tools can help programmers not to be lost, facing a host of patterns. In this paper, we propose that design patterns be systematically analyzed and reformulated to exhibit a reasoning binding solutions to precisely stated problems, given a set of mechanisms. We show on some creational patterns how these mechanisms can be described as program transformations and how an assistant tool could systematically explore the set of potential solutions induced by these transformations. This would both help the selection and the instantiation of the appropriate patterns. NOTE. — This paper is an improved and at least twice longer version of [ZIA 00]. RÉSUMÉ. Les modèles de conception ont largement participé à la diffusion de solutions éprouvées à des problèmes récurrents de conception et de programmation à objets. Pourtant le concept doit à présent évoluer pour que les programmeurs puissent gérer le nombre croissant de ces modèles. Dans ce papier nous proposons que les modèles de conception soient systématiquement analysés et reformulés pour exhiber un raisonnement liant les solutions à des problèmes décrit précisément, étant donné un ensemble de mécanismes. Nous montrons sur certains modèles créateurs comment ces mécanismes peuvent être décrits comme des transformations de programmes et comment un outil d'assistance pourrait systématiquement explorer l'ensemble des solutions possibles induit par ces transformations. Ceci faciliterait la sélection mais aussi l'instanciation des modèles appropriés. KEY WORDS:

design pattern, program transformation, prototype, factory method

MOTS-CLÉS :

modèle de conception, transformation de programme, prototype, fabrication

2

Titre de l’ouvrage

1. Introduction The concept of design pattern1 has had a large impact on object-oriented programming. The goal has been to "capture design experience in a form that people can use effectively" [GAM 94]. Indeed, design patterns have helped spreading a limited number of well-tried solutions to recurring object-oriented design problems. Unfortunately, it was predicted that "the number of design patterns will grow to a level, where it becomes impossible to maintain an impression of which design patterns exist, let alone to know what problems these design patterns actually solve" [AGE 98]. In fact, Gamma et al. already acknowledged that "With more than 20 design patterns in the catalog to choose from, it might be hard to find the one that addresses a particular design problem," [GAM 94]. Today, more than a thousand patterns have already been published [RIS 00]. Moreover, not only are there quite many patterns to choose from, but these patterns are often not very clearly defined. As pointed out by [EDE 99a], the authors of [GAM 94] already exhibited substantial difficulty to agree on the question whether a proposed Multicast pattern is indeed a special case of the Observer pattern [VLI 97a,b]. As another example, [AGE 98] considers Product Trader [RIE 96] (a.k.a. Late Creation) as a mere variation of Abstract Factory, which was realized only long after Product Trader was published. The solution described in detail in Product Trader is indeed briefly sketched as a possible implementation variation of Abstract Factory. So, Product Trader could be considered as a variation of Abstract Factory, but in fact these patterns address related but different problems2. The point is not only that some patterns overlap and that it might be difficult to decide where the borders are. The problem is more serious. Patterns were not designed to be executed or analyzed by computers [COP 94]. Patterns are to be executed by architects with "insight, taste, experience and a sense of aesthetics" [COP 94]. Nevertheless, the present situation requires tool support and at least partial automation, if only because there are simply too many patterns to cope with. Since patterns were not designed with tool support in mind, it is not surprising that a lot of work is necessary to reformulate the design and implementation knowledge which is underlying them, into a form which is usable by support tools. Unfortunately, if several proposals have been made to either formalize part of design patterns or to provide tool support, little has been published on the design knowledge itself that underlies patterns. As well explained in [EDE 99b], most research on formalizing design patterns or on providing tool support, has focused on the micro-architecture that describes the solution.

1

The idea to adapt Chistopher Alexander's concept of pattern in architecture [ALE 79] to objectoriented programming is due to [BEC 87]. The reader not familiar with design patterns should see http://hillside.net/patterns/. 2 Abstract factory makes assuptions on families of products, which Product Trader does not make.

Titre de la partie

3

[BUD 96] describes a tool that creates class declarations and definitions from a few pieces of information such as choices for names of classes or for design tradeoffs. [SUN 99] describes a tool that is able to generate code from the instantiation of meta-descriptions of the solution part of design patterns. [LAU 98] is a significant effort to describe patterns more abstractly than in [GAM 94] so that more general solutions can be described with a visual albeit rigorous notation. [EDE 99a] describes the generic micro-architecture of design patterns using a visual notation that is equivalent to a subset of higher order monadic logic. None of these approaches, however, exhibit the fundamental mechanisms that underlie design patterns. [BOS 96] proposes a programming language that extends the classical objectoriented model. In particular this language supports so-called layers that encapsulate objects and intercept messages. Several patterns can indeed be elegantly defined in terms of a few message management primitives. [EDE 97] proposes a metaprogramming approach in which the application of a pattern is expressed as an algorithm. [ROB 97] proposes a quite interesting approach that relies on refactoring primitives. This approach was used, for example, to retrofit the Visitor pattern. Despite their interest, these proposals and the other proposals that we are aware of, focus on the solution part of patterns. They do not pay attention to the problems addressed by patterns, nor to the links between these problems and solutions. Without the ability to systematically deduce a solution from the definition of the problem, design knowledge is clearly superficial. Moreover, since more than a thousand patterns have already been published, the problem of selecting the right pattern is becoming more and more important. A significant assistance must rely on a description on what problems each pattern addresses. Intuitively, a design pattern could be represented as a transformation from a problem statement to a solution. The problem statement could be described formally and solutions generated using program transformation techniques [PAR 90]. In order to further clarify the how our approach stands compared to related work on tool support for design patterns, it is convenient to use the classification of [BOR 99]. Note however that we have only implemented a simple prototype, yet, which explores the set of solutions induced by the transformation rules that will be presented below. Our main objectives are clearly assistance to design and code generation but reverse engineering and design validation might also take advantage of our approach. Concerning the kind of support to design patterns, [Bor 99] identified code generation from parameters selection, code generation from the instantiation of a meta-description of the solution part of patterns and finally, language support. This classification, which we think is quite accurate, is symptomatic of the complete lack of support of the problem part of patterns. As far as we know, since this classification was made, only [MAR 00a,b] and our own proposal have tried to address the problem part of design patterns. [MAR 00a,b]'s approach does not exactly rely on program transformation but on the refinement mechanism of B. This approach is certainly complementary to ours. Our proposal, as well as [MAR 00a,b]'s, have the ambition to go far beyond code generation from the selection of

4

Titre de l’ouvrage

parameters or from the instantiation of any description of design patterns which do not address their problem aspect. On the other hand our approach is certainly more difficult to implement and our prototype is far from integrated into a programming environment. Concerning language support, the "old debate over the mechanisms that belong in programming languages versus those that are better served by tools" [CHA 00] has indeed been revived by the success of design patterns. We do welcome improvements to programming languages and do acknowledge that the problems which some design patterns address can be better solved by mechanisms that can be included into languages. In fact we do agree with Craig Chambers' proposal that design patterns should be studied, as well as many other programming problems and solutions, to gain inspiration in developing new abstraction mechanisms [CHA 00]. We do not take side on whether these mechanisms should necessarily be included into programming languages. If this can be done while keeping languages reasonably simple and homogeneous then it is a good idea. Our proposal simply assumes that a set of available mechanisms has been identified. On the other hand, it is quite dubious that language support will ever be enough to completely capture programmers' experience or even best practices to the point that design patterns would become useless. Whatever the programming language a large expertise is required to create a complex application or system. Part of this expertise can be hard-wired into a compiler or an interpreter but this expertise is more related to the implementation of the mechanisms than to using these mechanisms to solve problems! This is a mere consequence of what a programming language is: a language to make programs rather than a language to specify applications or systems. Contrary to programming languages, transformational techniques can be used to express at least part of the expertise to go from the specification of problems to programs. The expertise which is harder to express is the control of these transformations. In this paper, we then propose that design patterns be augmented with program transformations to generate solutions from a declarative statement of the problem. If appropriately defined, these transformation rules can show how solutions can be generated from a few fundamental mechanisms. The level of detail of the solutions depends on which transformations are included or not, while the validity of solutions depends of the effort put to validate each transformation rule. How can this be used in practice? First, even without tool support these transformations could be useful to define design patterns more precisely. Thus there could be additional sections besides "intention", "applicability", "consequences" etc. which could be named "rigorous problem statement" and "transformations". Second, as pointed out by John Vlissides in [CHA 00], using a tool to generate code is quite useful to teach design patterns because no fixed text can show all the variations and trade-offs which can be chosen. Third, pattern selection and instantiation can be eased. More precisely, we propose that programmers use annotations to express the design problem they are facing and that a set of possible solutions be exposed to them in which they would choose. In this paper we propose one annotation for the

Titre de la partie

5

problem on which we have focused and claim that this annotation is not ad-hoc. We show that a few transformations are enough to find the solutions of the Prototype and Factory Methods patterns. So these patterns are not exactly selected but rather re-discovered. So the main contribution of this paper is to propose a new research topic which consists in improving design patterns using transformation techniques. Obviously this is an ambitious objective and this paper is only a first step in this direction. In the rest of this paper, we illustrate our proposal on one problem addressed by several design patterns, namely some of what [GAM 94] calls creational patterns. In section 2, we define this problem and briefly recall the definition of two creational patterns: Prototype and Factory Method. In section 3, we show how a few fundamental mechanisms, namely runtime dispatch, delegation and routine call, can be expressed using transformation rules. In section 4, we show how these rules solve the problem and generate the solutions of Prototype and Factory Method.

2. The virtual creation problem [GAM 94] distinguishes three kinds of design patterns: creational patterns, structural patterns and behavioral patterns. Creational design patterns "abstract the instantiation process. They help make a system independent of how its objects are created, composed and represented". More precisely, the main goal of these patterns, and especially of Factory Method and Prototype, is to solve the virtual creation problem. This problem consists in creating instances of an interface, that is instances of the classes that implement it, without explicitly naming those classes. The reader should be aware that the virtual creation problem is not a big issue in languages, like Smalltalk, where classes are objects. The typical language for which it is an issue is C++. According to the well known object-oriented principle which recommends to "program to an interface, not to an implementation", it is generally an excellent idea not to make the code using an interface depend on which classes implement it. Hence, clients of an interface manipulate instances of this interface, using runtime dispatch (a.k.a. dynamic binding) on virtual functions. The virtual creation problem comes from the fact that there is no obvious guarantee that runtime dispatch can be used to create instances: since by definition these instances do not exist yet, they cannot be used as targets for such a dispatch.

2.1 A motivating example This example is adapted from [GAM 94] where it was used to illustrate the Prototype pattern. Suppose you want to write a GraphicTool class to create and manipulate instances of an abstract Graphic class for graphical components.

6

Titre de l’ouvrage

More precisely, you have to create instances of concrete classes that inherit from Graphic, but you do not want your code to depend on which subclasses of Graphic exist. If the GraphicTool class is used, for example, to make an editor for music scores, the relevant classes could be those of Figure 1. This example is an instance of the virtual-creation problem in which the interface is that of the abstract class Graphic, the client is the GraphicTool class and the classes that implement the interface are the concrete subclasses of Graphic.

*UDSKLF7RRO

*UDSKLF 6WDII

0XVLFDO1RWH :KROH1RWH

+DOI1RWH

Figure 1. Classes for an editor of music scores

In order to create a graphical object, you may want to write this C++ code: Graphic* p = new xxx(); Code example 1.

where xxx is the name of the appropriate class. So, assuming Graphic and MusicalNote are abstract classes, xxx would in fact be Staff, WholeNote or HalfNote, if only you had the right to use those classes and knew which one to instantiate. So you would like to write code example 1 with syntax allowing an assistant tool to recognize an unknown class name. One example could be: Graphic* p = new %%X(); Code example 2. Optionally, some conditions could be added to restrict the set of classes to consider. In this example, the class to instantiate will certainly depend on some runtime condition: on which icon has the user clicked. In other cases, the class to instantiate might depend on information that is available at static time, but still unknown from, or unusable by the programmer.

Titre de la partie

7

The reader might think that introducing this "%%X" notation is quite ad-hoc. This is not the case. The concrete syntax is not the point here: what matters is that some part of the code has been made more generic. Introducing meta-variables, that is variables that denote a set of possible syntactic constructs, is not ad-hoc: it is a very general mechanism. Design patterns are quite often concerned with decoupling parts of an application so that they can vary independently. Meta-variables are a natural way to represent varying aspects. In our example, the variable that was introduced denotes a class identifier. Note that in Smalltalk for example, where the virtual creation problem is not as important as in C++, there is a global dictionary which can be used to get a class using its name and then apply new: "(Smalltalk at: aClassName) new". The Class Retrieval and Product Trader patterns [RIE 96] use meta-data to represent a predicate on the set of subclasses of a super-class. The advantage of such a meta-programming approach is that it does not require any special tool to run. We propose not to a priori decide how to evaluate such predicates. Sometimes, information on which class to use, can be resolved at compile time and does not require runtime support. Sometimes information can be exploited using mechanisms provided by the programming language and again no meta-data would be required in that case. We think that a set of transformation rules should express available options. Among the creational patterns two of them have been used a lot, are relatively simple, and do not make very restrictive assumptions: Prototype and Factory Method. Both patterns take advantage of the fact that an object "knows" which class it belongs to and that runtime dispatch can take advantage of this information.

2.2 The Prototype pattern The structure of this pattern is given in Figure 2, which was adapted from [GAM 94]. On this figure, the Prototype class corresponds to the Graphic class in Figure 1 and Client represents the class (say GraphicTool) which needs to create an instance of Graphic.

&OLHQW

prototype

Operation()

3URWRW\SH Clone()

p = prototype->Clone()

&RQFUHWH3URWRW\SH

&RQFUHWH3URWRW\SH

Clone()

Clone()

Figure 2. Structure of Prototype

8

Titre de l’ouvrage

The rationale of this pattern is to use a pre-created object of the desired class, the prototype, as a target for a virtual function, Clone, which will create an object of the same class. The prototype object stores the information on which class is to be instantiated. Since this information is used at runtime, this pattern can be used when the class to be instantiated is decided at runtime. In our editor example, a prototype can be associated to each icon identifying a kind of graphical object. It does not matter how exactly the information on which class to use is acquired, provided that the client has access to the prototype object at the time the instance of Graphic must be created. The Prototype pattern can also be used for storing initialization information and not simply information on which class to instantiate. As far as the virtual creation problem is concerned the Clone method should maybe be renamed CreateBrother: initialization information is not required to be stored in the prototype itself but could be passed as parameters.

2.3 The Factory Method pattern The structure of this pattern is given in Figure 3 adapted from [GAM 94]. On this figure, the Product class corresponds to the Graphic class in Figure 1. On the other hand, Creator does not necessarily correspond to the class that needs to create instance of Graphic. The FactoryMethod virtual function could be used by another class.

3URGXFW

&UHDWRU FactoryMethod() AnOperation()



product = FactoryMethod() …

&RQFUHWH3URGXFW

&RQFUHWH&UHDWRU FactoryMethod()

return new ConcreteProduct

Figure 3. Structure of Factory Method

The rationale of this pattern is to represent the information of which class to instantiate with an instance of a hierarchy parallel to that of Product. If the Creator hierarchy does not already exist, it must be created. The Creator hierarchy must be connected to the Product hierarchy: each version of FactoryMethod must know which ConcreteProduct to instantiate.

Titre de la partie

9

2.4 Virtual creation as a special case of generic routine invocation Virtual creation, that is to say the creation of an object of an unknown class, is very similar to the invocation of a virtual function. In both cases, some routine (function, method, operator,…) has to be selected indirectly in a set of versions of what we will call a generic routine. So we will reformulate virtual creation as a special case of a generalization of virtual function invocation. We think that it is crucial, when making precise what problems design patterns solve, to try and relate these problems to more general ones. Otherwise, there would be little contribution to easing the selection of patterns or to exhibiting the design knowledge that underlies them. A routine is a piece of code which has a name, which can be invoked with arguments, and which may or may not return a result. A generic routine is a set of routines that are called the versions of the generic routine. Examples of generic routines are virtual functions, multi-methods but also the set of new operators in C++ which have some given signature and which are associated with the subclasses of some given abstract class. A routine invocation is an expression r(x1,…,xn) where r is the name of a routine, and x1,…,xn are the arguments of the invocation. A generic routine invocation is a triple , where g is a generic routine, (x1,…,xn) is a list of actual arguments and p is the dispatch predicate which identifies one or more routines in g. If p identifies more than one routine, then the generic routine invocation is ambiguous. The dispatch predicate typically includes constraints on the name of the routine to call and type constraints. These constraints are typically implicit in programming languages. The generic routine invocation problem consists in generating code that actually invokes the routine determined by a generic routine invocation, with the appropriate arguments. This is a generalization of virtual function invocation and of generic function invocation that includes multimethods [AGR 91], since generic functions do not include routines such as the C++ new operator that cannot rely directly on runtime or multiple dispatch. Note that generic function invocation can also be ambiguous, especially in case of multiple inheritance or multi-methods and note that [AMI 96] proposed a tool to help solve such ambiguity. An interface is a set of generic routines. In statically typed languages, signatures are associated to routines and the dispatch predicate implicitly requires that signatures of versions of a routine must be more specific than those of their generic version3. A virtual creation such as the one of Code example 2, is then a special case of generic routine invocation where the routine is the set of new operators which match the signature. What is specific to virtual creation is simply that no virtual function is a priori available since the C++ new operator cannot use dynamic binding. Including

3

For the sake of uniformity we have included the type of the target in the signature of virtual functions.

10

Titre de l’ouvrage

the return type in signatures is unusual4, but in our case this is useful because without this additional constraint too many versions of the new operator would qualify. However, since only a static type will be provided as a return type, e.g. Graphic*, the call will still be ambiguous. The dispatch predicate can include additional constraints to the name and type (implicit) constraints. In fact this information is eventually required in some form or another so that the proper routine is called and not another one. The point though is that this is to be taken care of by the programmer not by the transformational support tool. To reflect this our transformation rules include the addition of information: an hypothetical variable can be postulated which will bear the necessary information. This kind of rule must be controlled tightly to avoid looping and a lot of our implementation effort went into the control of such dangerous rules as well as into avoiding as much as possible ad-hoc control. There is a priori no limit to the complexity of the transformations that could be applied if the dispatch predicate was actually made fully explicit. The fact that we make simplifying assumptions is not an intrinsic limitation of our approach but rather a practical simplification. More sophisticated transformation rules could always be added to support new mechanisms but this would go quite beyond the simpler idea of supporting design patterns. In our current implementation, the name and signature constraints are included in the transformation rules which is probably not a very good idea and should be revised in future versions.

3. Transformation rules Let us get back to the motivating example which was introduced above. A designer who wishes to create an instance of the Graphic abstract class has written the code of Code example 2. We will show how a few rules can progressively transform the term new %%X() and rediscover the solutions of the Prototype and Factory Method design patterns. These transformations have been written in Prolog but for the sake of legibility we present a simplified version. In particular, the terms that are manipulated are in fact augmented with a context, conditions on their variables and an environment representing the entities that have yet been created by the transformations. The context of a term is the routine in which it occurs. So the context of new %%X() would be some method of the GraphicTool class. Like in Prolog, rule variables start with a capital letter. For the sake of simplicity, the Prolog rules are not yet connected to C++ source directly and a description of C++ source code has to be written manually in Prolog.

4

Compilers only use the type of parameters, not the return type, to select the right version of a generic routine.

Titre de la partie

11

3.1 Recasting virtual creation as a generic routine invocation The first transformation reformulates an instance of a virtual creation in C++ (augmented with some syntax, here %%, to note meta-variables) into an instance of a generic routine invocation. This transformation has not been implemented yet, so that its result is written manually. Moreover, a meta-representation of the application under development is supposed to have been generated, which is yet also done manually. This representation is merely a way for the transformations to access information on which entities have been defined, yet, in the application (it is a repository of class definitions, routine signatures and variable definitions). new %%C (Args) → invoke (new(C), ReturnType, Args) Transformation 1. Virtual creation as generic routine invocation The generic routine is determined by the constraints expressed in the invoke term. The return type must be inferred from the context of the term. In our graphical editor example, the return type is required to be a subtype of Graphic*. Common software engineering or object-oriented constraints that foster maintenance have also been implemented. This includes encapsulation but also hiding the implementation of interfaces to their external clients. In our example, GraphicTool is not allowed to see the implementation of the Graphic abstract class. In C++ this means not using identifiers declared in subclasses of Graphic.

3.2 Direct routine call The simplest way to make an invocation is to directly call a routine. invoke (Name, ReturnType, Args) → directCall (Routine, Args) provided that lookupRoutine (Name, ReturnType, Args, Routine) Transformation 2. Direct routine call

The lookupRoutine condition returns a routine (a Prolog atom referencing the representation of a routine) matching the constraints on its name and signature. This condition also checks that the context of the left-hand-side term has the right to call Routine (i.e. respects encapsulation…). Remember that the terms shown here are simplified versions of those used in the implementation which also include their context (the routine in which their occur), additional constraints which could not be

12

Titre de l’ouvrage

checked yet (and might be left for the designer to enforce) and the entities which the tool has created while transforming the term. 3.3 Runtime dispatch If an appropriate virtual function is available, runtime dispatch can be used. invoke (Name, ReturnType, Args) → dynamicCall (VF, Target, OtherArgs) provided that lookupVirtualFunction (Name, ReturnType, Args, VF, Target, OtherArgs) Transformation 3. Runtime dispatch

The lookupVirtualFunction is similar to lookupRoutine but for virtual functions. Runtime dispatch will, eventually when the generated application runs, select a routine according to the dynamic type of Target. This target is either the first argument of Args or an hypothetical target that is introduced by the tool. It is the responsibility of the designer to guarantee that this target has been or will be correctly initialized.

3.4 Delegation In the virtual creation problem, the name of the class to be instantiated cannot be used, hence a direct call is not possible. Since new is not a virtual function, a dynamic call cannot be used directly either. On the other hand, it is possible to call a routine that calls new. Such a delegation that is heavily used in design patterns can be introduced by the well known fold transformation [DAR 75]. Term → invoke (DR, ReturnType, Args) provided that DR is a routine defined by DR (FormalArgs) = Term1 and Term1 is Term with formal instead of actual arguments Transformation 4. Fold (a.k.a. delegation) In this transformation DR (the Delegation Routine) is simply a routine which computes Term. So Term is replaced by a routine call which will compute it. In our graphical editor example, DR would be Clone and Term would be invoke(new(C),pointer(Graphic),[]). The result would be two invocation terms and a definition of Clone (in practice the tool will not coin the name "Clone" of course).

Titre de la partie

13

Note that delegation should be a low priority rule as it makes the problem a priori more complex: two invocations must be solved instead of one. It should only be used when the tool fails to get acceptable code. In practice this means that the tool is allowed to use rules such as delegation only if it is not able to generate a term of the target programming language. 4. Solving the virtual creation problem Applying the first rule to new %%X() would yield invoke (new(C), pointer(Graphic), []). In addition, the context, namely the routine of GraphicTool in which it occurs, is attached to the term new %%X(). A direct call to new(C) is not possible here because C is a variable (and no additional constraint can instantiate it)5. In fact even if C was instantiated the routines of GraphiTool cannot use the constructors (and then new) of subclasses of Graphic. A dynamic call is not possible either since new is not a virtual function. So without delegation, the tool is unable to solve the virtual creation problem. In some cases the designer may want to violate the rule which prevents from using subclasses directly and use, say, a C++ switch statement to decide the dispatch predicate with explicit tests. This is what we call a false Factory Method. This idiom is sometimes confused with the actual Factory Method design pattern, probably because the name "Factory Method" depicts the idiom better than the design pattern which bears the name. So assuming that the names of the subclasses of Graphic are unknown or must not be used, the tool fails to rewrite the original abstract term. Hence a delegation is now allowed which would yield two terms: invoke (DR, pointer(Graphic), []) and invoke (new(C), pointer(Graphic), []). However, the context of invoke (new(C), pointer(Graphic), []) is now routine DR so a direct call to new(C) is possible if DR has the right to use the subclasses of Graphic. There are two cases here depending on where the dispatch predicate is decided (that is where it is decided where to call the right routine). Deciding the dispatch predicate in the first invocation (in some method of GraphicTool), leads to a more complex variation of the false Factory Method mentioned above. Our current implementation does not consider this case nor the false Factory Method idiom by imposing rather strong constraints on the directCall transformation. If the dispatch predicate is decided in DR (as in our prototype) it is in principle possible to use yet another variation of false Factory Method where DR is the factory method and decides the dispatch predicate with explicit tests. Our current

5

Pending conditions are checked before each transformation when possible, which in our current implementation means when they are ground terms. This leads to new instanciations. In our example however meta-variable %%C would not in fact become a regular Prolog variable (which are implicitly existentially quantified) or at least its instanciation must be controlled closely (e.g. with freeze/thaw predicates).

14

Titre de l’ouvrage

implementation does not support this case which is sometimes acceptable and is very similar to the Virtual Constructor idiom [COP 92]. So if the dispatch predicate is decided in DR a direct call is not possible. On the other hand, invoke (DR, pointer(Graphic), []) can be transformed into a dynamic call, provided that DR is a virtual function. The value of C in the other term (called in DR) will be represented by the dynamic type of the target of the call to DR. Since there are no arguments among which to choose the target, it must be provided. It is assumed that the designer is able to provide access to such a target which has to be initialized somewhere in the application. This is precisely what both the Prototype and Factory Method design patterns do. In Prototype, DR is a virtual function of Graphic namely Clone. In Factory Method, DR is a virtual function of GraphicTool or of another class, and a hierarchy parallel to that of Graphic must be introduced if it does not already exist. Once again introducing such a hypothetical entity must be tightly controlled to avoid looping or unnecessarily complex solutions.

5. Conclusion In this paper we have proposed to augment the informal description of design patterns with transformation rules that will describe how fundamental mechanisms are used. This will improve the understanding of design patterns augmented this way, and will open new perspectives to their support by tools. We have applied this approach to the main problem addressed by creational patterns, namely what we have called the virtual creation problem. This problem was carefully defined and shown to be a particular case of a generalization of virtual function invocation. This analysis allowed us to identify several fundamental mechanisms that can be used to solve this generalized problem: direct routine call, runtime dispatch and folding/delegation. These mechanisms were expressed as transformation rules that were shown to be able to generate the solutions of the two most important creational patterns: Prototype and Factory Method. Other more or less important idioms were encountered while exploring the search space induced by our mere three transformations, which reveals the connections between these idioms and patterns and even clarifies a common confusion. So the result of our approach on the virtual creation problem would lead to some sort of pattern language dedicated to this problem. A "rigorous problem definition" section would define virtual creation like we did in this paper and relate it to generic routine invocation. The mechanisms and transformations would be introduced in dedicated sections (or rather mentioned since they would certainly be common to several pattern languages). Each pattern (Prototype and Factory Method) would then have a section to show how it can be rediscovered by these transformations. The rules mentioned in this paper (but the first one) have been implemented in Prolog. The Prototype pattern is rediscovered by our little engine for the GraphicTool example, and a virtual function is automatically attached to the

Titre de la partie

15

Graphic class but more complex examples should be tested and a larger part of C++ must yet be supported. Future work will include the development of an interactive tool to control these rules. Other mechanisms could be introduced, such as multiple dispatch and function pointers that would provide more alternative solutions. This should be done carefully though, as the induced combinatorial explosion will require additional control knowledge. On the other hand, many patterns aim at decoupling parts of an application using some sort of indirection, like creational patterns. In fact, many design patterns obviously use similar mechanisms, so that the approach used here for creational patterns is expected to adapt well to many more patterns.

Acknowledgements The author wishes to thank Jean-Pierre Briot, Dominique Gaïti, Jean-François Perrot and the anonymous referees for their help to improve the quality of this paper.

6. References [AGE 98] AGERBO E., CORNIS A., "How to preserve the benefits of Design Patterns", OOPSLA 1998. [AGR 91] AGRAWAL R., DEMICHIEL L. G., LINDSAY B. G., "Static Type Checking of MultiMethods", OOPSLA 1991. [ALE 79] ALEXANDER C., The Timeless Way of Building, Oxford University Press, 1979. [AMI 96] AMIEL E., DUJARDIN E., "Supporting Explicit Disambiguation of Multi-Methods", ECOOP 1996. [BEC 87] BECK K., CUNNINGHAM W., experiment report on design patterns at OOPSLA 1987. See http://c2.com/ppr/about/author/kent.html. [BOR 99] BORNE I., REVAULT N., "Comparaison d'outils de mise en oeuvre de design patterns", revue l'Objet, éditions Hermes, Volume 5, numéro 2, 1999. [BOS 96] BOSCH J., "Language Support for Design Patterns", TOOLS Europe '96. [BUD 96] BUDINSKY F. J., FINNIE M. A., VLISSIDES J. M., YU P. S., "Automatic code generation from design patterns", IBM Systems Journal, Vol. 35, No 2, 1996. [CHA 00] CHAMBERS C., HARRISSON B., VLISSIDES J., "A Debate on Language and Tool Support for Design Patterns", ACM POPL 2000, pp. 277-289. [COP 92] COPLIEN J.O., Advanced C++ - programming styles and idioms, Addison Wesley 1992. [COP 94] COPLIEN J.O, "Software Design Patterns: Common Questions and Answers", Proceedings of Object Expo New York, SIGS Publications, pp 39-42, 1994. Also appears in [RIS 98] pp 311-320 and available at http://hillside.net/patterns/papers/

16

Titre de l’ouvrage

[DAR 75] DARLINGTON J., "Applications of program transformation to program synthesis", International Symposium on Proving and Improving Programs, Arc-et-Senans, France, 1975. [EDE 97] A. H. EDEN, J. GIL, A. YEHUDAI, "Precise Specification and Automatic Application of Design Patterns", Proceedings of the 12th IEEE International Automated Software Engineering Conference, ASE '97, Lake Tahoe, Nevada, November 1997, pp. 143-152. Los Alamos: IEEE Computer Society Press. [EDE 99a] EDEN A. H., Y. HIRSHFELD, K. LUNDQVIST. "LePUS – Symbolic Logic Modeling of Object Oriented Architectures: A Case Study". Second Nordic Workshop on Software Architecture – NOSA 1999. [EDE 99b] EDEN A. H., Precise Specification of Design Patterns and Tool Support in their Application, Ph.D. dissertation, available at http://www.math.tau.ac.il/~eden/ [GAM 94] GAMMA E., HELM R., JOHNSON R., VLISSIDES J., Design Patterns: Elements of Reusable Object-oriented Software, Addison Wesley, Reading, 1994. [LAU 98] LAUDER A., KENT S., "Precise visual specification of design patterns", ECOOP 1998. [MAR 00a] R.MARCANO-KAMENOFF, N. LEVY, F. LOSAVIO, « Specification formelle de patterns d'architectures distribuees en UML et B », Langages et Modèles à Objets, LMO 2000. [MAR 00b] MARCANO-KAMENOFF R., "Formalizing Pattern Applicability - An Approach based on UML and B", Doctorial Symposium of the IEEE International Conference on Automated Software Engineering, ASE 2000. [PAR 90] PARTSCH H. A., Specification and Transformation of Programs, Springer-Verlag 1990. [RIE 96] RIEHLE D., "Patterns for Encapsulating Class Trees", in [VLI 96]. [RIS 00] RISING L., The Pattern Almanac 2000, Addisson Wesley 2000. [ROB 97] ROBERTS D., BRANT J., JOHNSON R., "A Refactoring Tool for Smalltalk", TAPOS 3(4): 253-263 ,1997 [SUN 99] SUNYÉ G., « Génération de code à l'aide de patrons de conception », Langages et Modèles à Objets – LMO 1999. [VLI 96] VLISSIDES J. , COPLIEN J. O., KERTH N., eds., Pattern Languages of Program Design 2, Addison Wesley 1996 [VLI 97a] VLISSIDES J., "Multicast". C++ Report, Sep. 1997. SIGS Publications. [VLI 97b] VLISSIDES J., "Multicast - Observer = Typed Message". C++ Report, Nov.-Dec. 1997. SIGS Publications. [ZIA 00] ZIANE M., "A Transformational Viewpoint on Design Patterns", IEEE int. conf. on Automated Software Engineering, ASE 2000.

Suggest Documents