Example Design Patterns
Let’s take a look at a few of the classics.
Adapter
In the adapter pattern you use an existing class (that doesn't quite
have the right interface) in your application through a new class with the
interface you need. In this example, we want to use an existing
Landscape class in our flight simulator application, but it doesn't
have the right interface, so we create an adapter class called
MountainScene:
- You can use the adapter pattern as a class adapter (the adapter is a
subclass of the adaptee) or an object adapter (the adapter contains the
adaptee and forwards to it).
Decorator
A decorator lets you dynamically add additional responsibilities
to an object. This example is taken from the Java Core API:
- More flexible than subclassing (you can't add and remove behavior once
you fix a subclass, but decorators are independent of the objects they
decorate and decorations can be added and removed).
- Better than subclassing when there are many things that you can attach behaviors to.
You don't want separate classes for CheckedZipFileInputStream,
BufferedByteArrayInputStream, BufferedPushbackDigestGZIPStringBufferInputStream,
.... With m basic classes and n decorators, subclassing would result in
m*2n classes.
- Sometimes you can't subclass anyway, e.g. the class is "final" in Java, or
you don't have the C++ source of the class.
- The decorator has the interface of the object it decorates so clients don't
know or care that the object is decorated.
- Decorators can be nested recursively.
- You can add a property twice, e.g. bordered bordered text.
State
The State pattern makes the behavior of an object related to its
state without polluting its operations with conditional statements.
It gives you the appearance that an object has changed its class.
- The state pattern localizes state-specific behavior, which would
otherwise be scattered about in conditional statements.
- It makes it easy to add new states.
- It makes it easier to guard against inconsistent states.
- State objects are often singletons.
- This pattern is common in drawing programs; the program behaves very
differently depending on which tool is currently selected (text tool,
select tool, magnifying glass, hand, pen, brush, bucket....)
Composite
Use the composite pattern when you have a hierarchy of objects and
you want to be able to treat groups of objects as a single object.
Here is an example from the Java Core API:
- Sometimes you will see an application of this pattern where any component
can be a container.
- Note that depending on the situation, you may or may not want the children
ordered. You may or may not want a container to "own" its components, etc.
Observer
The observer pattern has subjects and dependent observers; when a subject
changes state, all of its observers are automatically notified and updated.
The subject doesn't have to depend on who the observers actually are;
observers can register and unregister with the subject dynamically.
- The observer pattern shows up a lot in GUI applications that are smart
about separating data from presentation (models vs. views).
- The pattern supports broadcast communication.
- Push or pull? In the push model, the subject sends details to the
observers regarding what changed and possibly why; in the pull model
the subject just sends an "I changed" message and the observer has
to respond by querying the subject to ascertain what changed.
- Maybe an observer could register with multiple subjects; in this
case subjects have to send their identity to observers.
- In the case in which many events could trigger changes in the subject,
we might want a model in which observers only register with the
events that they are interested in.
Singleton
Ensure a class can only have one instance -- and provide a global
point of access to this instance.
- The client doesn't construct the sole instance.
- The instance is obtained from a
getInstance()
call,
which can be implemented to do delayed creation.
- It's also possible for the sole instance to be a public
static field of the class.
- Implementation Note: You must make all constructors private.
This also means you must define at least one constructor.
In Java and C++, if you do not define a constructor, a
"default" constructor is automatically generated, and this
autogened constructor is public!
Template Method
The template method pattern is defining the skeleton of an algorithm,
leaving some steps to subclasses.
- Template methods are extremely common.
- Use them to cleanly factor the commonality among subclasses so as to
avoid duplicating code.
- Use them to provide "hooks" at well-defined points.
- Note that template methods just let part of an algorithm vary, while
strategies vary the whole algorithm.
Factory Method
The Factory Method pattern is used when you have an interface for creating
an object, but you don't know (until run time) exactly which class
you want to instantiate.
The two main varieties are (1) the factory method takes in a parameter
indicating the kind of object to create, and (2) the creator is abstract
and all of its subclasses will override a
factory method to create instances of the right class. Here is
how the first alternative looks.
Abstract Factory
An abstract factory has several operations for creating particular
kinds of products; subclasses of the factory create the concrete
products. Use an abstract factory when you want your system to be
configured with a particular family of products, and you want to be
able to easily replace the family.
The classic typical example is a program which is independent
of a windowing system:
TODO .image abstractfactory.gif
- The client only knows about the abstract widgets.
- Adding new operations is hard.
getFactory()
is a factory method.
- The concrete factories are usually singletons.
Strategy
The Strategy pattern lets a system choose a particular policy for
implementing an operation at run time. A good example are the layout
managers in the Java Core API:
- Strategies let you factor out non-trivial algorithms from clients
or from other objects. In the AWT, we could have containers responsible
for laying out their components, but since there are multiple ways
to lay out components, things could really get complex!
- Without strategies, it is a mess to add new algorithms.
- Strategies provide yet another way to avoid subclassing :-)
- Strategies help to eliminate conditional statements.
- In some cases you may want to make strategies flyweights.
Memento
A memento is an encapsulation of another object's (the originator's) state
that a third object (the caretaker) can hold onto and send back to the
originator to support undo if necessary. Only the originator can
set and retrieve the state of the memento.
TODO .image memento.gif
- Implementable with friends in C++, and private inner classes in Java.
- Mementos simplify the originator: without them the originator must
manage the state the caretaker asked for.
- Think twice before using mementos if the state that must be saved is huge.
- Caretaker must remember to delete the mementos it obtains.
Command
A command object encapsulates a request. With commands you can queue or
log requests, implement macro commands or transactions, support undo,
etc. You can parameterize objects by actions to perfom, like menu items:
TODO .image command.gif
- A command could use a memento to support undo but it does not have to.
- Commands are the object-oriented replacement for callbacks.
- Commands eliminate conditional statements and make it easy to add
new commands without touching existing code.
Proxy
A proxy is a placeholder (or surrogate) for another object. Clients access
the remote object through the proxy. The proxy has the same interface
as the remote object.
- The client really doesn't have to know or care that the proxy is
even there; the client thinks it is talking to the real object.
- You can have remote proxies which are local stand-ins for objects in
separate address spaces (even separate machines), virtual
proxies for creating expensive objects only when needed,
and protection proxies to enforce access
rights on certain objects.
- The so-called smart pointers (or smart references) are basically proxies.
- Protection proxies are great because they decouple the security
policy from the service itself.
Iterator
The Iterator pattern gives you a way to access the elements of an
aggregate sequentially without exposing the underlying representation.
Clients don't even have to care what kind of collection they have.
TODO .image iterator.gif
- A great advantage of iterators is you don't have to bloat the interface
of the aggregate with details of the traveral mechanism(s). They are
especially useful if there is more than one traversal mechanism (e.g.
breadth-first, depth-first, keys-only, values-only, backwards,
forwards, inside-out, ...).
- The patterns allows there to be more than one pending traversal
at a time.
- External vs. internal iterators.
- You probably want to ensure the iterator is robust, i.e.
it still works even when you add to or delete from the aggregate
while there is an iteration pending. Or, you could just invalidate
the iterator, or make it throw an exception.
- The usual interface for an iterator has operations like setToBeginning(),
getCurrent(), advance(), and isDone(). There are several variations: you
could combine the second and third into a single operation getNext()
which returns the element and advances; you could get rid of isDone() by
making getNext() either throw an exception or return some kind of null
value; you could even make a use-once iterator and get rid of
setToBeginning().
- Can be nicely implemented as a private inner class of a collection.
Visitor
A visitor lets you perform an operation on a object structure. The
operation can be defined in one place instead of split up into separate
methods for each of the classes whose instances are in the object structure.
This lets you add new operations without changing the classes of the
elements on which they operate.
TODO .image visitor.gif
- A likely place to use visitors is in a compiler. The language being
compiled doesn't change much -- you don't add new semantic objects really --
but the operations you want to apply are many and do tend to expand over
time. For example you could have: type-check, pretty-print, optimize,
translate-to-Pentium, translate-to-Sparc, translate-to-Alpha,
find-unitialized-variables, etc. You don't want to bloat and
frequently hack up the classes of semantic entities (e.g. expressions,
declarations, etc.)
- Note visitors go through an object structure; iterators go through
a single object.
- Use visitors when the types of objects in the structure do not
change often, and they ways that they are connected are consistent.
Interpreter
The Interpreter pattern is used when you can express a problem as
a sentence in some language, represented by an abstract syntax tree.
There is a class for each non-terminal, and each class has an
interpret() method.
TODO .image interpreter.gif
- You don't have to have an interpret() method in each class; you could
interpret sentences in a language with a visitor!
- You may want to use flyweights for your terminal symbols when representing
sentences.
Builder
In the Builder pattern you separate the construction of a complex
object from its representation so the same construction process can be
used to build multiple representations. The director defines the
process; you pass a particular builder object to the director.
Builders make complex objects step by step.
TODO .image builder.gif
Prototype
In the Prototype pattern new objects are created by cloning
existing prototype objects.
TODO .image prototype.gif
- With prototypes you can create customizable objects without even
knowing the details of creating them.
- The particular classes don't even have to be known at compile
time: you can obtain a prototypical object from some data store
(e.g. it could have been serialized). The classes themselves
can even be loaded dynamically.
- The prototype pattern makes it easy to create a huge variety
of objects easily. Factory Methods and Abstract Factories don't.
- You can always manufacture new prototypes on the fly!
- Prototypical objects need not be jammed into an artificial
object hierarchy.
- Be careful about shallow copy vs. deep copy.
Bridge
In the Bridge pattern you decouple an abstraction from its implementation
so the two can vary independently.
TODO .image bridge.gif
Facade
A facade is a nice single interface to a (possibly huge) subsystem
of various classes.
TODO .image facade.gif
Mediator
A mediator is an object that defines how a set of objects interacts.
It simplifies things because the set of objects can communicate through
the mediator rather than having each of them refer to each other.
TODO .image mediator.gif
Chain Of Responsibility
Decouple a requestor from the object that handles the request by
having the request be passed along of chain of objects until one of
the objects handles it.
TODO .image chainofresponsibility.gif
Flyweight
A flyweight is essentially a shared object. Use them when you have
a very large number of ("fine grained") objects and storing all of them
would be inefficient.
TODO .image flyweight.gif