1: The pattern concept
“Design patterns help you
learn from others' successes instead of your own
failures[2].”
Probably the most important step forward
in object-oriented design is the “design patterns” movement,
chronicled in Design Patterns
(ibid)[3]. That
book shows 23 different solutions to particular classes of problems. In this
book, the basic concepts of design patterns will be introduced along with
examples. This should whet your appetite to read Design Patterns by
Gamma, et. al., a source of what has now become an essential, almost mandatory,
vocabulary for OOP programmers.
The latter part of this book contains an
example of the design evolution process, starting with an initial solution and
moving through the logic and process of evolving the solution to more
appropriate designs. The program shown (a trash sorting simulation) has evolved
over time, and you can look at that evolution as a prototype for the way your
own design can start as an adequate solution to a particular problem and evolve
into a flexible approach to a class of
problems.
What is a pattern?
Initially, you can think of a pattern as
an especially clever and insightful way of solving a particular class of
problems. That is, it looks like a lot of people have worked out all the angles
of a problem and have come up with the most general, flexible solution for it.
The problem could be one you have seen and solved before, but your solution
probably didn’t have the kind of completeness you’ll see embodied in
a pattern.
Although they’re called
“design patterns,” they really aren’t tied to the realm of
design. A pattern seems to stand apart from the traditional way of thinking
about analysis, design, and implementation. Instead, a pattern embodies a
complete idea within a program, and thus it can sometimes appear at the analysis
phase or high-level design phase. This is interesting because a pattern has a
direct implementation in code and so you might not expect it to show up before
low-level design or implementation (and in fact you might not realize that you
need a particular pattern until you get to those phases).
The basic concept of a pattern can also
be seen as the basic concept of program design: adding a layer of
abstraction. Whenever you abstract
something you’re isolating particular details, and one of the most
compelling motivations behind this is to separate things that change from
things that stay the same. Another way to put this is that once you find
some part of your program that’s likely to change for one reason or
another, you’ll want to keep those changes from propagating other changes
throughout your code. Not only does this make the code much cheaper to maintain,
but it also turns out that it is usually simpler to understand (which results in
lowered costs).
Often, the most difficult part of
developing an elegant and cheap-to-maintain design is in discovering what I call
“the vector
of change.” (Here, “vector” refers to the maximum gradient and
not a container class.) This means finding the most important thing that changes
in your system, or put another way, discovering where your greatest cost is.
Once you discover the vector of change, you have the focal point around which to
structure your design.
So the goal of design patterns is to
isolate changes in your code. If you look at it this way, you’ve been
seeing some design patterns already in this book. For example,
inheritance can be thought of as a design pattern (albeit
one implemented by the compiler). It allows you to express differences in
behavior (that’s the thing that changes) in objects that all have the same
interface (that’s what stays the same). Composition
can also be considered a pattern, since it allows you to
change—dynamically or statically—the objects that implement your
class, and thus the way that class works.
You’ve also already seen another
pattern that appears in Design Patterns: the
iterator (Java 1.0 and 1.1
capriciously calls it the Enumeration; Java 2
containers use “iterator”). This hides the particular implementation
of the container as you’re stepping through and selecting the elements one
by one. The iterator allows you to write generic code that performs an operation
on all of the elements in a sequence without regard to the way that sequence is
built. Thus your generic code can be used with any container that can produce an
iterator.
Pattern taxonomy
One of the events that’s occurred
with the rise of design patterns is what could be thought of as the
“pollution” of the term – people have begun to use the term to
mean just about anything synonymous with “good.” After some
pondering, I’ve come up with a sort of hierarchy describing a succession
of different types of categories:
- how we write code in a particular language to do
this particular type of thing. This could be something as common as the way that
you code the process of stepping through an array in C (and not running off the
end).
- Design: the solution that we came up
with to solve this particular problem. This might be a clever design, but it
makes no attempt to be general.
- Design: a
way to solve this kind of problem. A design that has become more general,
typically through reuse.
- Pattern: how to
solve an entire class of similar problem. This usually only appears after
applying a standard design a number of times, and then seeing a common pattern
throughout these
applications.
I feel this
helps put things in perspective, and to show where something might fit. However,
it doesn’t say that one is better than another. It doesn’t make
sense to try to take every problem solution and generalize it to a design
pattern – it’s not a good use of your time, and you can’t
force the discovery of patterns that way; they tend to be subtle and appear over
time.
One could also argue for the inclusion of
Analysis Pattern and Architectural Pattern in this
taxonomy.
Design Structures
One of the struggles that I’ve had
with design patterns is their classification – I’ve often found the
GoF approach to be too obscure, and not always very helpful. Certainly, the
Creational patterns are fairly straightforward: how are you going to
create your objects? This is a question you normally need to ask, and the name
brings you right to that group of patterns. But I find Structural and
Behavioral to be far less useful distinctions. I have not been able to
look at a problem and say “clearly, you need a structural pattern
here,” so that classification doesn’t lead me to a solution
(I’ll readily admit that I may be missing something
here).
I’ve labored for awhile with this
problem, first noting that the underlying structure of some of the GoF patterns
are similar to each other, and trying to develop relationships based on that
similarity. While this was an interesting experiment, I don’t think it
produced much of use in the end because the point is to solve problems, so a
helpful approach will look at the problem to solve and try to find relationships
between the problem and potential solutions.
To that end, I’ve begun to try to
collect basic design structures, and to try to see if there’s a way to
relate those structures to the various design patterns that appear in well
thought-out systems. Currently, I’m just trying to make a list, but
eventually I hope to make steps towards connecting these structures with
patterns (or I may come up with a different approach altogether – this is
still in it’s formative stages).
Here[4]
is the present list of candidates, only some of which will make it to the final
list. Feel free to suggest others, or possibly relationships with
patterns.
• Encapsulation: self containment and embodying a model of usage
- Gathering
- Localization
- Separation
- Hiding
- Guarding
- Connector
- Barrier/fence
- Variation
in
behavior
- Notification
- Transaction
- Mirror:
“the ability to keep a parallel universe(s) in step with the golden
world”
- Shadow
“follows your movement and does something different in a different
medium” (May be a variation on
Proxy).
Design principles
When I put out a call for ideas in my
newsletter[5], a
number of suggestions came back which turned out to be very useful, but
different than the above classification, and I realized that a list of design
principles is at least as important as design structures, but for a different
reason: these allow you to ask questions about your proposed design, to apply
tests for quality.
- Principle of least
astonishment (don’t be
astonishing).
- Make
common things easy, and rare things
possible
- Law
of Demeter: “don’t talk to strangers:” an object should
only reference itself, its attributes, and the arguments of its
methods.
- Subtraction:
a design is finished when you cannot take anything else
away.
- Simplicity
before
generality[6]. (A
variation of Occam’s Razor, which says “the simplest solution
is the best”). A common problem we find in frameworks is that they are
designed to be general purpose without reference to actual systems. This leads
to a dizzying array of options that are often unused, misused or just not
useful. However, most developers work on specific systems, and the quest for
generality does not always serve them well. The best route to generality is
through understanding well-defined specific examples. So, this principle acts as
the tie breaker between otherwise equally viable design alternatives. Of course,
it is entirely possible that the simpler solution is the more general
one.
- Reflexivity
(my suggested term). One abstraction per class, one class per abstraction. Might
also be called
Isomorphism.
- Independence
or Orthogonality. Express independent ideas independently. This
complements Separation, Encapsulation and Variation, and is part of the
Low-Coupling-High-Cohesion
message.
- Once and
once only: Avoid duplication of logic and structure where the duplication is
not accidental, ie where both pieces of code express the same intent for the
same reason.
In the process of
brainstorming this idea, I hope to come up with a small handful of fundamental
ideas that can be held in your head while you analyze a problem. However, other
ideas that come from this list may end up being useful as a checklist while
walking through and analyzing your
design.
The Singleton
Possibly the simplest design pattern is
the singleton, which is a way to provide one and
only one object of a particular type. This is used in the Java libraries, but
here’s a more direct example:
//: c01:SingletonPattern.java
// The Singleton design pattern: you can
// never instantiate more than one.
// Since this isn't inherited from a Cloneable
// base class and cloneability isn't added,
// making it final prevents cloneability from
// being added through inheritance:
final class Singleton {
private static Singleton s = new Singleton(47);
private int i;
private Singleton(int x) { i = x; }
public static Singleton getReference() {
return s;
}
public int getValue() { return i; }
public void setValue(int x) { i = x; }
}
public class SingletonPattern {
public static void main(String[] args) {
Singleton s = Singleton.getReference();
System.out.println(s.getValue());
Singleton s2 = Singleton.getReference();
s2.setValue(9);
System.out.println(s.getValue());
try {
// Can't do this: compile-time error.
// Singleton s3 = (Singleton)s2.clone();
} catch(Exception e) {
e.printStackTrace(System.err);
}
}
} ///:~
The key to creating a
singleton is to prevent the client programmer from having any way to create an
object except the ways you provide. You must make all
constructors private, and you
must create at least one constructor to prevent the compiler from
synthesizing a default constructor
for you (which it will create as “friendly”).
At this point, you decide how
you’re going to create your object. Here, it’s created statically,
but you can also wait until the client programmer asks for one and create it on
demand. In any case, the object should be stored privately. You provide access
through public methods. Here, getReference( ) produces the
reference to the Singleton object. The rest of the interface
(getValue( ) and setValue( )) is the regular class
interface.
Java also allows the creation of objects
through cloning. In this example, making the class final prevents
cloning. Since Singleton is inherited directly from Object, the
clone( ) method remains protected so it cannot be used (doing
so produces a compile-time error). However, if you’re inheriting from a
class hierarchy that has already overridden clone( ) as
public and implemented Cloneable, the way to prevent cloning is to
override clone( ) and throw a CloneNotSupportedException as
described in Appendix A. (You could also override clone( ) and
simply return this, but that would be deceiving since the client
programmer would think they were cloning the object, but would instead still be
dealing with the original.)
Note that you aren’t restricted to
creating only one object. This is also a technique to create a limited pool of
objects. In that situation, however, you can be confronted with the problem of
sharing objects in the pool. If this is an issue, you can create a solution
involving a check-out and check-in of the shared
objects.
Classifying patterns
The Design Patterns book discusses
23 different patterns, classified under three purposes (all of which revolve
around the particular aspect that can vary). The three purposes are:
- Creational:
how an object can be created. This often involves isolating the details of
object creation so your code isn’t dependent on what types of objects
there are and thus doesn’t have to be changed when you add a new type of
object. The aforementioned Singleton is classified as a creational
pattern, and later in this book you’ll see examples of Factory
Method and
Prototype.
- Structural:
designing objects to satisfy particular project constraints. These work with the
way objects are connected with other objects to ensure that changes in the
system don’t require changes to those
connections.
- Behavioral:
objects that handle particular types of actions within a program. These
encapsulate processes that you want to perform, such as interpreting a language,
fulfilling a request, moving through a sequence (as in an iterator), or
implementing an algorithm. This book contains examples of the Observer
and the Visitor
patterns.
The Design
Patterns book has a section on each of its 23 patterns along with one or
more examples for each, typically in C++ but sometimes in Smalltalk.
(You’ll find that this doesn’t matter too much since you can easily
translate the concepts from either language into Java.) This book will not
repeat all the patterns shown in Design Patterns since that book stands
on its own and should be studied separately. Instead, this book will give some
examples that should provide you with a decent feel for what patterns are about
and why they are so important.
After years of looking at these things,
it began to occur to me that the patterns themselves use basic principles of
organization, other than (and more fundamental than) those described in
Design Patterns. These principles are based on the structure of the
implementations, which is where I have seen great similarities between patterns
(more than those expressed in Design Patterns). Although we generally try
to avoid implementation in favor of interface, I have found that
it’s often easier to think about, and especially to learn about, the
patterns in terms of these structural principles. This book will attempt to
present the patterns based on their structure instead of the categories
presented in Design
Patterns.
The development challenge
Issues of development, the UML process,
Extreme Programming.
Is evaluation valuable? The Capability
Immaturity Model:
Pair programming
research:
Exercises
- SingletonPattern.java
always creates an object, even if it’s never used. Modify this program to
use lazy initialization, so the singleton object is only created the
first time that it is
needed.
- Using
SingletonPattern.java as a starting point, create a class that manages a
fixed number of its own objects. Assume the objects are database connections and
you only have a license to use a fixed quantity of these at any one
time.
[3]
But be warned: the examples are in
C++.
[4] This
list includes suggestions by Kevlin Henney, David Scott, and
others.
[5] A free
email publication. See www.BruceEckel.com to subscribe.
[6] From
an email from Kevlin Henney.