Example Design Patterns
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*2 n 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.
- The client does not care what the actual product classes are!!
- The factory method can be static or not static.
- For a non-static factory method, you can get further indirection by making a separate factory class, or go even further with a factory interface.
- Factory methods are good for parallel hierarchies.
- If you find that there are too many ifs or switches in your factory method, you can make a hashtable whose values are constructors. This is also helpful because code like this
Image createImage(String extension) < if (extension.equals("gif") < return new GIFImage(); >else if (extension.equals("jpg") < return new JPEGImage(); >>
could be hard to extend.
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:
- 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.
- 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:
- 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.
- 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.
- 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.
- 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.
Prototype
In the Prototype pattern new objects are created by cloning existing prototype objects.
- 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.
Facade
A facade is a nice single interface to a (possibly huge) subsystem of various classes.
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.
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.
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.