Principles of Object-Oriented Software Development
[] readme course preface 1 2 3 4 5 6 7 8 9 10 11 12 appendix lectures resources

talk show tell print

A catalogue of design patterns

subsections:


Why patterns, you may wonder. Why patterns and why not a method of object-oriented design and an introduction in one or more object-oriented languages? The answer is simple. Patterns bookmark effective design. They fill the gap between the almost infinite possibilities of object-oriented programming languages and tools and the rigor of methodical design. As Brian Foote expressed it in  [POPL3], patterns are the footprints of design, paving the way for future designs. They provide a common design vocabulary and are also helpful in documenting a framework. And, as we will see later, patterns may also act as a target for redesign, that is when the current design no longer offers the desired functionality and flexibility.

A catalogue of design patterns

 [GOF94]



slide: A catalogue of design patterns

The Gang of Four book, Design Patterns by  [GOF94], was immediately recognized as an important contribution to object-oriented software development. Not only because of the actual patterns presented, but also because of the style in which they were presented, crisp problem-oriented descriptions of actual solutions to real design problems, written with scientific rigor and accuracy. As Brian Foote remarked, actual design became a legitimate subject of computer science research.

The pattern schema, or rather a simplified version thereof, is depicted in slide pattern-schema. Each pattern must have a name, which acts as a handle in discussions about the design. Being able to speak about specific pattern solutions, such as a factory, greatly facilitates discussions about design.


pattern schema


Name -- handle

  • increases design vocabulary

Problem -- when to apply

  • explains the problem and the conflict

Solution -- general arrangement

  • design, responsibilities, collaborations

Consequences -- tradeoffs

  • to understand the costs and benefits

slide: The pattern schema

Other important entries in the pattern schema are, the problem indicating what the patterns is all about, the solution describing the general arrangment of the classes and objects involved, and the consequences or tradeoffs that a particular solution entails. The actual patterns presented in  [GOF94] are the result of the authors' involvement in developing various GUI toolkits, in particular Interviews,  [Interviews], and ET++,  [ET], and applications such as, for example, interactive text and image editors. In the course of developing a toolkit or application there are many occasions for redesign. Reasons why you may need to redesign are listed in slide redesign-patterns, along with an appropriate selection of patterns from  [GOF94].

design for change

  • creating an object by specifying a class explicitly -- Abstract Factory, Factory Method, Prototype
  • dependence on specific operations -- Chain of Responsibilty, Command
  • dependence on hardware & software platforms -- Abstract Factory, Bridge
  • dependence on object implementation or representation --Abstract Factory, Bridge, Memento, Proxy
  • algorithm dependence -- Iterator, Strategy, Template Method, Visitor
  • extending functionality by subclassing -- Bridge, Composite, Decorator, Observer
  • tight coupling -- Abstract Factory, Bridge, Chain of Responsibilities, Command, Facade, Mediator, Observer
  • inability to alter classes conveniently -- Adaptor, Decorator, Visitor

slide: Causes for redesign

Following  [GOF94], we may distinguish between creational patterns that govern the construction and management of objects, structural patterns that define the static relationships between objects, and behavioral patterns that characterize the dynamic aspects of the interaction between objects.

In this section we will look at a brief overview of the classification as originally presented in  [GOF94]. The patterns themselves will be treated only briefly. The reader is invited to consult the original source and the many publications that followed:  [POPL1],  [POPL2],  [POPL3].

Creational patterns

Design for change means to defer commitment to particular object implementations as long as possible. Due to inheritance, or rather subtyping, the client, calling a particular method, can choose the most abstract class, highest in the hierarchy. However, when it comes to creating objects, there seems to be no other choice than naming the implementation class explicitly. Wrong. Creational patterns are meant to take care of that, that is to hide the actual class used as far away as possible.

Creational patterns

  • Factory -- hide concrete classes
  • Factory Method -- virtual constructors
  • Prototype -- dynamic creation by cloning
  • Singleton -- one instance only

See Pattern Lectures


slide: Creational patterns

Creational patterns come in various flavors. In section delegation-in-Java some example realizations were presented. The factory class, for example, is a rather static way of hiding the implementation classes. As an alternative, you may use a factory method, similar to the instance method of the singleton class.

If you prefer a more dynamic approach, the prototype pattern might be better. A prototype is an object that may be used to create copies or clones, in a similar way as instances are created from a class. However, cloning is much more dynamic, the more so if delegation is used instead of inheritance to share resources with some ancestor class. See section prototypes.

The advantage of using a factory, or any of the other creational patterns, is that exchanging product families becomes very easy. Just look for example at the Java Swing library. Swing is supported under Unix, Windows and MacOS. The key to multiple platform support is here, indeed, the use of factories to create widgets. Factories are also essential when using CORBA, simply because calling a constructor is of no use for creating objects on a remote site.

Structural patterns

Objects rarely live in isolation. In slide structural-patterns a selection of the structural patterns treated in  [GOF94] is collected. Structural patterns indicate how classes and objects may be composed to form larger structures.

Structural patterns

  • object and class composition

PatternAliasRemarks
Composite part/whole collections of components
Flyweight handle/body extrinsic state, many objects
Adaptor wrapper resolve inconsistency between interfaces
Bridge handle/body relate abstraction to implementation
Decorator handle/body to introduce additional functionality
Facade handle/body provides unified interface
Proxy handle/body to defer ... remote, virtual, protection
Imagine, for example, an application for interactive text processing. Now, the Composite pattern may be used to combine text, images and also compound components, that may itself consist of other components.

Closely related to the Composite pattern is the Flyweight pattern, which is needed when the number of components grows very large. In that case, the components themselves must for obvious reasons carry as little information as possible. Context or state information must then be passed as a parameter.

To give some more examples, suppose there exists a nice library for formatting text and images, but unfortunately with only a procedural interface. Then the Adaptor pattern may be used to provide a interface that suits you, by wrapping the original library.

The Bridge pattern is in some sense related to the Factory. In order to work with a platform-independent widget library, you need, as has been explained, a factory to hide the creation of widgets, but you also need to bridge a hierarchy of platform-dependent implementation classes to the more abstract platform-independent widget set.

When creating widgets to display text or images it may be very inconvenient to create a separate class, for example when adding scrolling functionality. The Decorator pattern allows you to insert additional functionality without subclassing.

Now think of a networked application, for example to be able to incorporate components that are delivered by a server. The library may provide a number of networking classes that deal with all possible communication protocols. To simplify access to these classes a Facade may be built, hiding the complexity of the original class interfaces.

Alternatively, remote components may be available through a proxy. The Proxy pattern describes how access may be regulated by an object that acts as a surrogate for the real object. Like composites and decorators, proxies may be used for recursive composition. However, proxies primarily regulate access, whereas decorators add responsibilities, and composites represent structure.

Behavioral patterns

Our final category of patterns, behavioral patterns, concern the construction of algorithms and the assignment of responsibilities to the objects that cooperate in achieving some goal.

Behavioral patterns

cooperation

  • algorithms and the assignment of responsibilities between objects

class

  • Template Method -- the skeleton of an algorithm
  • Interpreter -- to evaluate expressions

object

composition

  • Mediator -- provides indirection
  • Chain of Responsibility -- connect objects to interact
  • Observer -- to handle dependencies

slide: Behavioral patterns

A first distinction can be made between patterns that involve the composition of classes (using inheritance) and patterns that rely on object composition.

As an example of the Template Method pattern, think of a compiler class that offers methods for scanning and the creation of a parse tree. Each of these methods may be refined without affecting the structure of the compilation itself.

An interpreter allows for evaluating expressions, for example mathematical formula. Expressions may be organised in a hierarchy. new kinds of expressions can be inserted simply by filling in details of syntax and (semantic) evaluation.

Object composition, which employs the handle/body idiom and delegation, is employed in the Mediator pattern, the Chain of Responsibility pattern and the Observer pattern. The actual task, such as for example updating the display of information when the actual information has changed, is delegated to a more specialized object, to achieve a loose coupling between components. The difference between a mediator and chain of responsibility is primarily the complexity of co-ordinating the tasks. For example, changing the format of a single image component from one image type to another image type may be done simply by using an image converter (mediator), whereas exporting the complete document to a particular format such as HTML may involve delegating control to a specialized converter that itself needs access to the original components (chain of responsibility). We will discuss the Observer pattern in more detail later.


Encapsulating behavior

objectify!

  • Command -- action + undo
  • Strategy -- choice of algorithms
  • Visitor -- decouple traversal and operations
  • Iterator -- access and traversal
  • State -- object state -> behavioral change

slide: Encapsulating behavior

A common characteristic of the patterns listed in slide objectify is that functional behavior is realized as an object. Semantically, objects are more powerful than functions, since objects can carry a state. Hence, the imperative objectify pays off when we need functions that must know their invocation history.

As an example of the Command pattern, think of how you would realize insertion and deletion commands in an interactive editor, with undo! Turning these commands into an object in which the information necessary for undoing the command can be stored, for example having a snapshot of the state stored in a Memento, it suffices to stack the actual command objects. To undo a command, pop the stack and invoke the undo method.

The Strategy pattern may be used to hide the details of the various layout algorithms that are available. For example, you may use a straightforward algorithm that formats the text line by line, or you may use the much more advanced formatting algorithm of \TeX, which involves the minimalization of penalties. These alternatives can be collected in a formatting strategy hierarchy, that hides the implementation details from the client by a common interface.

When doing the formatting, you may wish to separate the traversal of the component tree structure from the actual formatting operations. This may be accomplished by employing the Visitor pattern. In general it is recommended to abstract from the data structure and use a more abstract way, such as an Iterator or Visitor to access and traverse it.

The State pattern is similar to the dynamic role switching idiom that has been discussed in section hush-patterns. As an example, think of providing viewers for alternative document formats, such as VRML or PDF, in your application. Using the State pattern, it suffices to have a single viewer that changes itself according to the format of the document viewed.

The Observer pattern

The Observer pattern is a variant of the famous Model-View-Control (MVC) pattern, that governed the creation of the graphical user interface of the Smalltalk environment and many Smalltalk applications.

Observer

  • one-to-many dependencies and notification

Consequences

  • abstract coupling between subject and observer
  • constraint propagation
  • deals with unexpected updates

slide: Observer pattern

The basic idea is simple, to decouple information management and the display of information. In other words, a distinction is made between the model or subject, that carries the information, and the views or observers, that present that information in some format. As a consequence, when a change occurs, the viewers or observers have only to be notified to update their presentation.

In effect, MVC or the Observer pattern can be regarded as a simple method for constraint propagation. An advantage is that unexpected updates can be easily dealt with.



slide: The Observer pattern

The objects involved in realizing the Observer pattern are depicted in slide figure-observer. The subject object must allow for observers to be attached and detached. Note that observers must also have a reference to the subject. In particular, concrete observers must know how to obtain information about the state of the subject, to be able to update their view. What the abstract subject and observer classes supply are the facilities for attachment and mechanisms for notification and updates.

In the implementation of the Observer pattern there are a number of problems and tradeoffs that must be considered. For example, do we allow one observer to be attached to more than one subject? Do we allow for alternative update semantics, for example observer-pull instead of subject-push? Do we provide facilities for specifying aspects of interest, so that updates only need to concern those aspects? And finally, how do we guarantee mutual consistency between subjects and observers when we do allow for alternative update semantics?



[] readme course preface 1 2 3 4 5 6 7 8 9 10 11 12 appendix lectures resources
eliens@cs.vu.nl

draft version 0.1 (15/7/2001)