Design patterns
Design patterns are coding idioms that help build better programs. The goal is often to help make programs more modular by decoupling communicating code modules. Some design patterns just help avoid mistakes. Design patterns give programmers a common vocabulary for explaining their designs and aid in quick understanding of the advantages and disadvantages of particular designs.
The term design pattern was introduced by the very influential “Gang of Four” book, Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. The book discusses object-oriented programming and identifies more than 20 design patterns.
Many other design patterns and variations thereof have been identified since, some more useful and meaningful than others. In this lecture we look at some of the more important ones to understand why they are helpful. Understanding patterns will also help you resist the lure of the Cargo Cult Programming antipattern, in which design patterns are used without real purpose.
Iterator pattern
A common problem when designing programs is how to set up a stream of information from a producer module A to a consumer module B, while keeping both A and B decoupled so that each has no dependency on the module they are communicating with. Assuming that the values communicated have some type T, the communication we want can be depicted as follows:

The Iterator design pattern is one way to solve this programming problem. Module A constructs objects that provide the ability for the consumer to “pull” values from the producer. These object provide an interface like the following:
Iterator.java
This is a polling-style interface, in which the consumer can ask any
time for a new object, but might have to wait until something is available.
The next object is retrieved from the Iterator by calling next()
. To detect the
end of the iteration without using exceptions, it is standard to use the
hasNext()
method.
Once consumer B has obtained an iterator from producer A, it can keep getting new elements from the iterator without mentioning A in any way. The producer code doesn't need know about B, either. Thus, we have complete decoupling of A and B.
An additional advantage of this pattern is that multiple consumers can obtain streams of information from a single producer without interfering with each other. Whatever state is needed to keep track of the position in the stream is stored in the iterator object, not in the producer.
Java provides some convenient syntactic sugar for invoking iterators. A statement of the form
for (T x : c) { ... body ... }
is syntactic sugar for
foreach.java
To use this syntactic sugar, it is necessary that c
either be
an array or implement the interface Iterable<T>
:
Iterable.java
Implementing iterators
Iterators are very handy and easy for client code to use. They are a welcome addition to interfaces. However, there is one problem: implementing them can be tricky.
The iterator needs to keep track of the current state of the iteration so that it can resume at the right place in the stream on each call to
next()
. For tree data structures, tracking iteration state is particularly awkward. The state of the iteration is a path from the root to the current node. This path must be updated on each call tonext
.The iterator supports both
hasNext()
andnext()
methods. ThehasNext()
method must figure out whether there is a next element to be provided. Typically, this duplicates work that thenext()
might have to do, and in some cases, that work cannot be done separately by the two methods. The iterator must contain additional state to keep track of whether the current answer tohasNext()
has been computed yet.-
Dealing with changes to the underlying data structure during iteration is often tricky, so changes to the collection being iterated over is typically forbidden. In the Java collections framework, collection classes throw a
ConcurrentModificationException
if an element is requested from an iterator after a mutation to the collection that occurred during the iteration. Note that a concurrent modification can happen even if there is no real concurrency in the system. To detect such requests, every collection class object has a hidden version number that is incremented after each mutation. Iterator objects record the collection's version number when they are created, and compare this version number against the collection's on each call tonext()
. A mismatch causes the exception to be thrown.A commonly desired change to the collection is to remove the element currently referenced by the iterator. Iterators may support a
remove()
method whose job it is to remove the current element; this operation is not considered a concurrent modification. However, if there are multiple iterators traversing the data structure, aremove()
by one iterator will in general break the others.
Generators
Some languages other than Java support another language construct that
makes it easier to implement iterators. The C#, Python, Ruby languages support
generators that send results to the consumer using the
yield
statement. An extended version of Java that supports
yield
is JMatch, developed at Cornell. In these
languages, one can think of the iterator as running concurrently with the
consumer, but only when the consumer requests a new value. The iterator and
the loop body are coroutines. For example, with
generators, an iterator for trees can be implemented very easily using recursion:
tree_iterator.java
By contrast, a Java implementation of the same iterator will take at least 50 lines of code and offer more opportunities for introducing bugs. On the other hand, a careful Java implementation of a tree iterator can be made to run faster than the generator, by avoiding yielding elements up through every level of the tree. The trick is to keep the path from the root to the current node in a stack.
Ironically, the term iterator originally referred to this style of implementing iteration, which was invented in the language CLU in the 70's. The term generator originally referred to what we now know as the iterator design pattern.
Observer pattern
Sometimes we want to send a stream of information from a producer to a consumer, but it's not convenient to have the consumer polling the producer. Instead, we want to push information from the producer to the consumer. We can think of the information being pushed as events that the consumer wants to know about. This is the idea behind the Observer pattern, which works in the opposite way as the Iterator pattern:

In the Observer pattern, the consumer provides an object implementing the interface
Observer<T>
:
Observer.java
Whenever the producer has a new event x
to report to the
consumer, it calls the observer's method notify(x)
. The observer
then does something with the data it receives that is appropriate for the
consumer. Since the observer is provided by the consumer, it knows what
operations the consumer has and is typically inside the consumer's abstraction
boundary, perhaps implemented as an inner class.
How does the producer know which observers to notify? This is accomplished by registering the observer(s) with the producer. The producer implements an interface similar to this:
Observable.java
When the producer receives a call to registerObserver
, it records
the observer in its collection of observers to be notified. When the producer
has a new event to provide to consumers, it iterates over the collection,
calling notify
on each observer in the collection.
Java Swing listeners are an instance of the observer pattern.
For example, ActionListeners
are observers with a
notify
named actionPerformed
. If one is setting
up listener l
for button clicks, the Observable
in question
is the JButton
object, and the listener is registered by
calling addActionListener(l)
.
Like the Iterator pattern, the Observer pattern has the benefit that the producer and consumer can exchange information without tying either implementation to the other. The only information they share is the type of events being listened for. An observable can also provide information to multiple observers simultaneously.
We can see that there is a symmetry to Iterators and Observers. We can
make this a bit more compelling. Using A → B to represent the type of a function
that takes in an A and returns a B, and using () to represent the type of an
empty argument list (which is really the same thing as void
), we
have the following types:
Iterator: next: () → T iterator: () → (() → T) Observer: notify: T → () registerObserver: (T → ()) → ()
The types of the Iterator operations are exactly the same as the types of the Observer operations, except that all the arrows are flipped! This shows that we have a duality between Iterator and Observer.
Abstract Factory pattern
When we create objects using a constructor, we tie the calling code to a particular choice of implementation. For example, when creating a set, we specify exactly which implementation we are using (for simplicity, let's ignore type parameters):
Set s = new HashSet();
One way to avoid binding the client code explicitly to an implementing class is to use factory methods (creators), which we have talked about earlier. We might declare a class with static methods that create appropriate data structures:
class DataStructs { static Set createSet() { return new HashSet(); } static Map createMap() { return new HashMap(); } static List createList() { return new LinkedList(); } ... }
Now the client can create sets without naming the implementation, and the choice of which implementations to
use for all the data structures has been centralized in the DataStructs
class.
Sometimes static factory methods still don't provide enough flexibility. The choice of implementation is still fixed at compile time even if the client code doesn't choose it explicitly. We can solve this problem by using the Abstract Factory pattern. The idea is to define an interface with non-static creator methods for the various kinds of things that need to be allocated.
interface DataStructs { Set createSet(); Map createMap(); List createList(); ... }
All the choices about what implementation to use can now be bound into an object that
implements this interface. Assuming that object is in a variable ds
, the client
might contain:
DataStructs ds; ... Set s = ds.createSet();
Of course the choice of implementation has to be made somewhere, where
ds
is initialized, but that can be far away from the uses of
ds
, in some other module. Since the abstract factory is an object,
it can be chosen truly dynamically, at run time. There can even be multiple
implementations of an abstract factory interface used within the same program.
One place where the abstract factory approach has been used successfully is for user interface libraries. We might define an interface for creating UI components:
interface UIFactory { Button createButton(String label); Label createLabel(String txt); Scrollbar createScrollbar(); ... }
Then, different UIFactory
objects can encapsulate different choices
of look and feel for the user interface. Swing doesn't take quite this approach, but
the look-and-feel choices that make Swing UIs look different on Windows versus Mac OS
are in fact made by binding each Swing JComponent to a contained object of type UIComponent.
The UIComponent controls the look and feel of the JComponent, and it is chosen dynamically
based on the OS platform being used.
Singleton pattern
Sometimes classes never need to have more than one instance. A class with just one instance is an example of the Singleton pattern. For example, if we wanted a class that represented empty linked lists, we might only allocate a single object of that class, since all empty lists are interchangeable anyway. We can store it into a static field of the class to expose it to clients, and hide the constructor since it shouldn't be used outside the class itself:
class EmptyList implements List { public static EmptyList empty = new EmptyList(); private EmptyList() {} }
The Singleton pattern is also frequently used with the Abstract Factory pattern.
There is no need to have more than one object of the class implementing, say,
DataStructs
or UIFactory
in the examples above.
Composite
The Composite pattern is a pattern that we've already been using: it refers to using a data structure of objects to provide what appears to the client to be a single object. This idea is simply the combination of data structures with data abstraction. Even common objects like strings are Composite objects in Java.
Flyweight
The idea of this design pattern is to have objects that take up very little memory. This is done by having their representations be small and also by placing as much state as possible in underlying objects that are shared across many instances.
Interning
A related idea to flyweight objects is interning (known in Lisp as hashconsing). A hash table is used to keep track of all objects of a given class. Object creation is done by a factory method. When a new object is requested to be created, the factory method uses the parameters to the calls to look up whether a suitable object has already been created. If so, this object is returned. Otherwise, a new object is created using the constructor, which is typically made private so the only way to create objects is to go through the factory method. This pattern makes the most sense for immutable abstractions, because it may cause the same objects to be shared across unrelated code or data structures.
Adapter
The Adapter pattern allows an existing object to satisfy an interface it was not originally designed to satisfy, hiding the actual interface provided by the existing object. This is accomplished by using a wrapper object that implements the interface and that simply redirects calls of the new interface to the appropriate calls on the underlying wrapped object.
Decorator
The Decorator pattern is similar to the Adapter pattern. Here the idea is to extend the interface of some existing objects of a class. Unlike in the Adapter pattern, the Decorator interface is a subtype of the interface that the objects already implement; its implementation is a wrapper class that redirects all calls from the original interface to the wrapped object.
External state
Sometimes it is undesirable to record some of the state associated with an object in the object itself, perhaps because the class cannot be extended with new instance variables, because only some of the objects of the class have that extra state, or because that state is involved in an invariant maintained by another module. A second class is defined to contain that external state, and objects of the second class are created as necessary. To allow quickly finding the external state for an object, the external state objects are put into a hash table, using the original object itself as a key to find the state.
Visitor
The Visitor pattern allows the traversal of a tree data structure (such as an abstract syntax tree) to be factored out from the nodes of the tree, in a generic way that can be reused for multiple traversals. There are many variations on the visitor pattern.
State machine
Programming in an event-driven style can result in messy designs in which not all events are handled. One way to ensure all events are handled is to think about the program, or about parts of the program, as state machines. Often state machines are presented as mathematical abstractions, but they are also a way to organize code: a design pattern called the state design pattern.
A state machine has a set of states and a set of events that can occur. At any given moment, the machine is in one of the allowed states. However, when it receives a new event, it can change states. For each (state, event) pair, there is a new state to which the machine transitions when that event is received in that state.
A state machine can be represented as a graph in which the nodes are the states and the edges are the transitions between states. The edges are labeled with the event that causes that transition.
As a simple example, consider a window in a graphical user interface. Simplifying a bit, it can be in the following states: opened, closed, minimized, or maximized. (One reason that these states are a simplification is that the window also has a size and position.) The following events can be received: open, close, minimize, and maximize, corresponding to buttons that can be clicked. As a graph, the window implements the following state machine:

A diagram like this helps understand what states the system can get into and how the system moves among states. It doesn't help as much with ensuring that all combinations of states and events are considered. To ensure no combinations are missed, we can construct a state-transition table.
When the number of states in a state machine is finite, s state-transition table is called a finite state machine, or finite-state automaton. In general, a state machine can have an infinite number of states, or a very large number of states. The rows in the table correspond to states, and the columns correspond to events. The entries in the table say what the next state is, given the current state and event.
State | open | close | minimize | maximize |
---|---|---|---|---|
1. Opened | — | 2 | 3 | 4 |
2. Closed | — | — | — | — |
3. Minimized | 1 | 2 | — | — |
4. Maximized | — | 2 | 3? | 1 |
The table helps us think systematically about all the possible things that can happen in the system and be sure we have covered all the possibilities. Thinking about the various entries helps us discover missing event handlers and missing states. For example, when minimizing a maximized window, the state machine above forgets that the window was maximized. When the window is reopened, it will no longer be maximized. If that is not the desired behavior, we'll need to add a fifth state to the state machine, keeping track of windows that are minimized from a maximized state.
Even the entries marked with —, which represent events that don't make sense in the current state, are interesting to think about because we need to make sure that the user interface doesn't permit those events to happen—perhaps by graying out the corresponding UI component.
In the state design pattern, the various events that can be received by the state machine are represented as different methods on the state machine object. The key is to centralize the code that implements a state machine. It is also possible to implement a state machine as a big switch statement in which the event type is used to dispatch to the appropriate handling code, but this style of implementation is less object-oriented.
Model-View-Controller
Since the UI components are used to manipulate the information managed by the application, it is tempting to store that information and the algorithms that manipulate it (the application logic) directly in components, perhaps by using inheritance. This is usually a bad idea. The code for graphical presentation of information is different from the underlying operations on that information. Interleaving them makes the code harder to understand and maintain. Moreover, it makes it difficult to port the application to a new platform. For example, you might implement the application in Swing and then want to port it to Android, whose UI toolkit is very different. So it is a good idea to keep UI code and the application logic separate.
This observation leads to the Model-View-Controller pattern, in which the application classes are separated into one of three categories: the model, which contains the important application state and operations, and does not refer to the graphical UI classes; the view, which provides a graphical view of the model; and the controller, which handles user input and translates it into either changes to the view or commands to be performed on the model.

The idea is that the view may hold some state, but only state related to how the model is currently being displayed, or what part of the model is displayed. If the view were destroyed, some version of it could be created anew from the model. With this kind of structure, there can be more than one user interface built on top of the same model. We can even have multiple coexisting views.
One of the challenges of the MVC pattern is how to allow the view to update when the model changes without making the model depend on the view. This task is usually accomplished using the Observer pattern. The model allows observers to be registered on its state; the view is then notified when the state changes.

This separation between model, view, and controller will be very important for Assignment 7, in which you will build a distributed version of the critter simulation. The model will run on a shared server with one or more clients viewing that model through a user interface.
There are many variations of the MVC pattern. Some versions of the MVC pattern make less of a distinction between the view and the controller; this is usually indicated by talking about the M/VC pattern, in which the view and the controller are more tightly coupled, but strong separation is maintained between the model and these two parts of the design.
Antipatterns
There are also coding patterns that are used frequently, but are best avoided. These are often dubbed “antipatterns”. For example, some Java programmers make heavy use of reflection in Java. Using reflection is generally bad practice, leading to slow, fragile code. A good reason to use reflection is if you are loading code dynamically at run time (for example, for plugins or for dynamic code generation). Most applications do not need this capability, so we will not talk much about reflection in this course. A good and rather humorous list of antipatterns can be found on Wikipedia.