next up previous
Next: Issues Involved in Up: Introduction Previous: Introduction

Background

The production of highly reusable software components is a fundamental goal of software engineering. While code reuse has long been acknowledged as a valuable engineering practice, design reuse has received less attention. The software engineering community has only recently been provided with a clear notion of reusable design constructs, in the form of design patterns as presented by Gamma, Helm, Johnson, and Vlissides [5]. The patterns have been fundamental in helping the software engineering community recognize forms of evolution for objects in an application domain. Each design pattern identifies an aspect of a system that may vary, and proposes a way of writing programs such that the variation is possible. Table 1 contains a partial list of design patterns from [5], along with the variation each is intended to support.

  
Table 1: Variations defined by design patterns

 

  
Figure 1: Object model for line breaking strategy

As an example, the Strategy pattern allows a family of interchangeable algorithms to be defined and encapsulated. This allows the algorithm to vary independently of the clients that use it [5]. Thus, a strategy allows multiple implementations to be defined for a single interface. An example of the strategy pattern is shown in Figure 1, based on the example given in [5]. The figure contains a partial object model using the OMT diagram notation [19] for representing the relation between a Composition class used to maintain and update the line-breaks of text displayed in a text viewer, and a Compositor class which represents different variations of the line-breaking algorithm. For instance, SimpleCompositor implements a simple default line-breaking technique, TexCompositor implements a complex line-breaking technique for a TeX viewer, while ArrayCompositor provides a fixed number of elements per line. The aggregation relation is used to link the Compositor and Composition classes, while the inheritance relation is used to group the alternative implementations of the line-breaking strategy. Note the Compositor abstract class serves only to provide a common interface for the concrete line-breaking strategy alternatives.

The Compose method defined for the Compositor class simply delegates a request along its compositor reference to the appropriate strategy object, passing its self reference as an argument to allow the strategy to work on the Composition object. Note that the implementations defined in the Compositor hierarchy are actually intended to perform actions on the Composition object that receive the original Compose message.

  
Figure 2: Strategy pattern

The generic form of the strategy pattern is shown in Figure 2. A Receiver class has a method m. A Strategy class hierarchy is defined to provide alternative implementations of the method m, with each concrete strategy subclass representing a particular variation. An instance of the receiver class will reference a strategy object. The implementation of the m method for the receiver class simply delegates the request to its strategy, passing its self reference along as an argument. The m method defined in the concrete strategy is executed to perform the correct variation of the algorithm for the receiver object.

There are several reasons for using an aggregation relation to link the receiver and strategy classes rather than simply defining subclasses of the receiver class to implement the method m. First, there may be many algorithms of the receiver class that should be varied, and it would be undesirable to define subclasses to cover all permutations. More importantly, it may be necessary to vary dynamically which strategy is used for a receiver class instance. The static inheritance relation cannot support such dynamic variation.

An alternative would be to provide methods with different names for each implementation, or to have a method that takes a parameter to indicate which implementation should be executed, or to use a multi-method approach. These alternatives however require the client, which is the object that will make the call, to understand enough of the details of the receiver's implementation to be able to indicate which implementation they would like. This violates class encapsulation by requiring the client class to know details about the implementation of the receiver class.

Like the Strategy pattern, all of the design patterns in [5] are described using constructs such as the static inheritance and aggregation relations, with examples of each pattern given in C++ [4]. The code unintentionally demonstrates how awkward it is to program natural concepts of behavioral evolution in a static, class-based language. We will now examine the nature of and reasons for this awkwardness.



next up previous
Next: Issues Involved in Up: Introduction Previous: Introduction



Karl Lieberherr
Tue Jan 21 09:24:10 EST 1997