A catalogue of design patterns
Subsections:
-
Creational Patterns
-
Structural Patterns
-
Behavioral Patterns
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 possibilies 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
-
a common design vocabulary
-
documentation and learning aid
-
an adjunct to existing methods
-
a target for redesign
The Gang of Four book, Design Patterns, 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 below . 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.
The 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
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.
Causes for Redesign
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
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
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
| Pattern: Composite |
Alias: Part/whole |
Remarks: collections of components |
|
slide: Structural Patterns
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 coordinating 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
hierachy, 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 anstract 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 hush-patterns. As an example, think of
providing viewers for alternative document formats, such as VRML or PDF,
into 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, 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 . 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?