Automating the Application of Design Patterns: A Refactoring Approach

Precursors

Much of the existing work on design pattern transformations assumes as a starting point what can be termed a green field situation. By this we mean that when the design pattern transformation is applied to the program, the components that take part in the transformation do not already have any existing relationships pertaining to the pattern. Consequently these approaches do not support the breaking of existing relationships as part of the transformation process. From a software evolution perspective this is inadequate because in an existing program the basic intent of the pattern may well exist in the code already, but in a way that is not amenable to further program evolution. For example, in the case of the Factory Method pattern, the Creator class may already create and use instances of a Product class, but not in the flexible manner that allows easy extension to other Product classes. (See Appendix A for a brief description of the Factory method pattern.)

At the other extreme there is the antipattern approach. In this approach the assumption is made that the programmer has failed to appreciate the need for the pattern in the first instance, and has used some inadequate design structure to deal with the situation. The philosophy behind this approach is that the code may have been developed by a programmer who was not aware of patterns. For example, in the case of the Factory Method pattern, the client of the Creator class may have to configure it with a flag to tell it what type of Product class to create. We discovered several problems with the antipattern approach:

For these reasons we use a different starting point for our transformations. For a large class of design patterns, the effect of the pattern may be viewed as making certain program evolutions easier. This suggests that in the simple case the design pattern is not needed, but as future changes in requirements demand greater flexibility from the software, it becomes necessary. For example, it is frequently the case that a class A creates an instance of a class B, but normally this relationship does not require the application of a design pattern. However a future change in the requirements may well require that the class A have the flexibility to work with any one of a number of different subclasses of B, and so the need for the Factory Method pattern arises. The programmer of the original system did not make an error of judgement; software systems will always evolve in ways that the original creators simply cannot foresee. Indeed, applying a design pattern where it is not needed is highly undesirable as it introduces an unnecessary complexity to the system.

This leads us to our description of a precursor: a precursor is a design structure that expresses the intent of a design pattern in a simple way, but that would not be regarded as an example of poor design. This is not a formal definition, but it serves to exclude both the green field situation where there is no trace of the intent of the pattern in the code, and the antipattern situation where the programmer has tried to resolve the problem in an inadequate way.

For example, the precursor we use for the Factory Method pattern is simply this: the Creator class must create an instance of the Product class. This condition may appear to be trivial, but it is a natural precursor to the Factory Method pattern. The Creator class creates and uses an instance of the Product class and while this is adequate for the moment, a new requirement may demand that the Creator class be able to work with other types of Product class and this will require the application of the Factory Method pattern.

Contact the author.

© Mel Ó Cinnéide 2000