Bruce Eckel - Thinking in Patterns. Problem-Solving Techniques Using Java
Bruce Eckel - Thinking in Patterns. Problem-Solving Techniques Using Java
in
Patterns
Problem-Solving
Techniques using Java
Bruce Eckel
President, MindView, Inc.
Please note that this document is in its initial form, and much remains to
be done. Corrections and comments should go to
http://www.topica.com/lists/TIPatterns/
Contents
Preface
Introduction
11
What is a pattern?..............11
Pattern taxonomy ............. 13
Design Structures ............. 14
Design principles .............. 15
The Singleton .................... 17
Classifying patterns .......... 18
The development challenge19
Exercises ...........................20
2: Unit Testing
20
32
Template method..............33
Exercises ...........................34
34
Tools.................................. 61
Exercises ........................... 61
63
78
80
6: Function objects
89
90
92
103
103
Creating a language..........113
Controlling the interpreter117
Putting data in ............................ 117
Getting data out ..........................123
Multiple interpreters...................126
134
10: Callbacks
139
140
142
Observer.......................... 142
Observing flowers .......................144
148
159
Abstracting usage.............177
Multiple dispatching....... 180
Implementing the double dispatch181
167
155
13: Projects
202
Preface
The material in this book has been developed in
conjunction with a seminar that I have given for several
years, a couple of times with Larry OBrien, then with Bill
Venners. Bill and I have given many iterations of this
seminar and weve changed it many times over the years
as we both have learned more about patterns and about
giving the seminar.
In the process weve both produced more than enough information for us
each to have our own seminars, an urge that weve both strongly resisted
because we have so much fun giving the seminar together. Weve given
the seminar in numerous places in the US, as well as in Prague (where we
try to have a mini-conference every Spring together with a number of
other seminars). Weve occasionally given it as an on-site seminar, but
this is expensive and difficult to schedule, because there are two of us.
A great deal of appreciation goes to the people who have participated in
these seminars over the years, and to Larry and Bill, as they have helped
me work through these ideas and to refine them. I hope to be able to
continue to form and develop these kinds of ideas through this book and
seminar for many years to come.
This book will not stop here, either. After much pondering, Ive realized
that I want Thinking in Python to be, initially, a translation of this book
rather than an introduction to Python (there are already plenty of fine
introductions to that superb language). I find this prospect to be much
more exciting than the idea of struggling through another language
tutorial (my apologies to those who were hoping for that).
Introduction
This is a book about design that I have been working on
for years, basically ever since I first started trying to read
Design Patterns (Gamma, Helm, Johnson & Vlissides,
Addison-Wesley, 1995), commonly referred to as the
Gang of Four1 or just GoF).
There is a chapter on design patterns in the first edition of Thinking in
C++, which has evolved in Volume 2 of the second edition of Thinking in
C++, and youll also find a chapter on patterns in the first edition of
Thinking in Java (I took it out of the second edition because that book
was getting too big, and also because I had decided to write this book).
This is not an introductory book. I am assuming that you have worked
your way through Thinking in Java or an equivalent text before coming
to this book.
In addition, I assume you have more than just a grasp of the syntax of
Java. You should have a good understanding of objects and what theyre
about, including polymorphism. Again, these are topics covered in
Thinking in Java.
On the other hand, by going through this book youre going to learn a lot
about object-oriented programming by seeing objects used in many
different situations. If your knowledge of objects is rudimentary, it will
get much stronger in the process of understanding the designs in this
book.
1: The pattern
concept
Design patterns help you learn from others' successes
instead of your own failures1.
Probably the most important step forward in object-oriented design is the
design patterns movement, chronicled in Design Patterns (ibid)2. 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 didnt have
the kind of completeness youll see embodied in a pattern.
Although theyre called design patterns, they really arent 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 youre 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 thats likely to change for one reason or
another, youll 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-tomaintain 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, youve 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 (thats the thing that changes) in objects that all
have the same interface (thats what stays the same). Composition can
also be considered a pattern, since it allows you to changedynamically
or staticallythe objects that implement your class, and thus the way that
class works.
Youve 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 youre 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 thats 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, Ive come up with a sort of hierarchy describing a
succession of different types of categories:
1. Idiom: 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).
2. Specific 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.
3. Standard Design: a way to solve this kind of problem. A design
that has become more general, typically through reuse.
4. Design 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 doesnt say that one is better than another. It
doesnt make sense to try to take every problem solution and generalize it
to a design pattern its not a good use of your time, and you cant 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 Ive had with design patterns is their
classification Ive 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 doesnt
lead me to a solution (Ill readily admit that I may be missing something
here).
Ive 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 dont 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, Ive begun to try to collect basic design structures, and to try
to see if theres a way to relate those structures to the various design
patterns that appear in well thought-out systems. Currently, Im 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 its formative stages).
Here3 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.
Gathering
Localization
Separation
This list includes suggestions by Kevlin Henney, David Scott, and others.
Hiding
Guarding
Connector
Barrier/fence
Variation in behavior
Notification
Transaction
Design principles
When I put out a call for ideas in my newsletter4, 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.
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 heres 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
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:
1.
2.
3.
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. (Youll find that this doesnt 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 its 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:
Exercises
1.
2.
2: Unit Testing
One of the important recent realizations is the dramatic
value of unit testing.
This is the process of building integrated tests into all the code that you
create, and running those tests every time you do a build. Its as if you are
extending the compiler, telling it more about what your program is
supposed to do. That way, the build process can check for more than just
syntax errors, since you teach it how to check for semantic errors as well.
C-style programming languages, and C++ in particular, have typically
valued performance over programming safety. The reason that
developing programs in Java is so much faster than in C++ (roughly twice
as fast, by most accounts) is because of Javas safety net: features like
better type checking, enforced exceptions and garbage collection. By
integrating unit testing into your build process, you are extending this
safety net, and the result is that you can develop faster. You can also be
bolder in the changes that you make, and more easily refactor your code
http://www.junit.org
intended to make the creation of test code easy, I wanted to see if I could
make it even easier, applying the Extreme Programming principle of do
the simplest thing that could possibly work as a starting point, and then
evolving the system as usage demands (In addition, I wanted to try to
reduce the amount of test code, in an attempt to fit more functionality in
less code for screen presentations). This chapter is the result.
Describe what the code is supposed to do, not with some external
graphical tool but with code that actually lays the specification
down in concrete, verifiable terms.
2.
3.
Provide a way to verify when the code is finished (when all the tests
run correctly).
Thus, if you write the tests first then testing becomes a development tool,
not just a verification step that can be skipped if you happen to feel
comfortable about the code that you just wrote (a comfort, I have found,
that is usually wrong).
You can find convincing arguments in Extreme Programming Explained,
as write tests first is a fundamental principle of XP. If you arent
convinced you need to adopt any of the changes suggested by XP, note
that according to Software Engineering Institute (SEI) studies, nearly
70% of software organizations are stuck in the first two levels of SEI's
scale of sophistication: chaos, and slightly better than chaos. If you
change nothing else, add automated testing.
Writing tests
Writing tests becomes very simple. Heres an example that creates the
necessary static inner class and performs trivial tests:
//: c02:TestDemo.java
// Creating a test
import com.bruceeckel.test.*;
public class TestDemo {
private static int objCounter = 0;
private int id = ++objCounter;
public TestDemo(String s) {
System.out.println(s + ": count = " + id);
I had originally called this assert(), but that word became reserved in JDK 1.4
when assertions were added to the language.
}
public void close() {
System.out.println("Cleaning up: " + id);
}
public boolean someCondition() { return true; }
public static class Test extends UnitTest {
TestDemo test1 = new TestDemo("test1");
TestDemo test2 = new TestDemo("test2");
public void cleanup() {
test2.close();
test1.close();
}
public void testA() {
System.out.println("TestDemo.testA");
affirm(test1.someCondition());
}
public void testB() {
System.out.println("TestDemo.testB");
affirm(test2.someCondition());
affirm(TestDemo.objCounter != 0);
}
// Causes the build to halt:
//! public void test3() { affirm(false); }
}
} ///:~
The test3( ) method is commented out because, as youll see, it causes
the automatic build of this books source-code tree to stop.
You can name your inner class anything youd like; the only important
factor is that it extends UnitTest. You can also include any necessary
support code in other methods. Only public methods that take no
arguments and return void will be treated as tests (the names of these
methods are also not constrained).
The above test class creates two instances of TestDemo. The TestDemo
constructor prints something, so that we can see it being called. You could
also define a default constructor (the only kind that is used by the test
framework), although none is necessary here. The TestDemo class has a
close( ) method which suggests it is used as part of object cleanup, so
this is called in the overridden cleanup( ) method in Test.
The testing methods use the affirm( ) method to validate expressions,
and if there is a failure the information is stored and printed after all the
tests are run. Of course, the affirm( ) arguments are usually more
complicated than this; youll see more examples throughout the rest of
this book.
Notice that in testB( ), the private field objCounter is accessible to the
testing codethis is because Test has the permissions of an inner class.
You can see that writing test code requires very little extra effort, and no
knowledge other than that used for writing ordinary classes.
To run the tests, you use RunUnitTests.java (which will be introduced
shortly). The command for the above code looks like this:
java com.bruceeckel.test.RunUnitTests TestDemo
It produces the following output:
test1: count = 1
test2: count = 2
TestDemo.testA
Cleaning up: 2
Cleaning up: 1
test1: count = 3
test2: count = 4
TestDemo.testB
Cleaning up: 4
Cleaning up: 3
All the output is noise as far as the success or failure of the unit testing is
concerned. Only if one or more of the unit tests fail does the program
returns a non-zero value to terminate the make process after the error
messages are produced. Thus, you can choose to produce output or not,
as it suits your needs, and the test class becomes a good place to put any
printing code you might needif you do this, you tend to keep such code
around rather than putting it in and stripping it out as is typically done
with tracing code.
If you need to add a test to a class derived from one that already has a test
class, its no problem, as you can see here:
//: c02:TestDemo2.java
// Inheriting from a class that
// already has a test is no problem.
import com.bruceeckel.test.*;
public class TestDemo2 extends TestDemo {
public TestDemo2(String s) { super(s); }
Theres one other caveat, which will also provide a little review of Java
packages. If you want to be completely rigorous, you must put your blackbox test class in a separate directory than the class it tests, otherwise it
will have package access to the elements of the class being tested. That is,
youll be able to access protected and friendly elements of the class
being tested. Heres an example:
//: c02:Testable.java
public class Testable {
private void f1() {}
void f2() {} // "Friendly": package access
protected void f3() {} // Also package access
public void f4() {}
} ///:~
Normally, the only method that should be directly accessible to the client
programmer is f4( ). However, if you put your black-box test in the same
directory, it automatically becomes part of the same package (in this case,
the default package since none is specified) and then has inappropriate
access:
//: c02:TooMuchAccess.java
import com.bruceeckel.test.*;
public class TooMuchAccess extends UnitTest {
Testable tst = new Testable();
public void test1() {
tst.f2(); // Oops!
tst.f3(); // Oops!
tst.f4(); // OK
}
} ///:~
You can solve the problem by moving TooMuchAccess.java into its
own subdirectory, thereby putting it in its own default package (thus a
different package from Testable.java). Of course, when you do this,
then Testable must be in its own package, so that it can be imported
(note that it is also possible to import a package-less class by giving the
class name in the import statement and ensuring that the class is in your
CLASSPATH):
//: c02:testable:Testable.java
package c02.testable;
public class Testable {
Running tests
The program that runs the tests makes significant use of reflection so that
writing the tests can be simple for the client programmer.
//: com:bruceeckel:test:RunUnitTests.java
// Discovering the unit test
// class and running each test.
package com.bruceeckel.test;
import java.lang.reflect.*;
import java.util.Iterator;
public class RunUnitTests {
public static void
Automatically executing
tests
Exercises
1.
Install this books source code tree and ensure that you have a
make utility installed on your system (Gnu make is freely
available on the internet at various locations). In TestDemo.java,
un-comment test3( ), then type make and observe the results.
2.
3.
3: Building
application
frameworks
An application framework allows you to inherit from a class or set of
classes and create a new application, reusing most of the code in the
existing classes and overriding one or more methods in order to
customize the application to your needs. A fundamental concept in the
application framework is the Template Method which is typically hidden
beneath the covers and drives the application by calling the various
methods in the base class (some of which you have overridden in order to
create the application).
Template method
An important characteristic of the Template Method is that it is defined in
the base class and cannot be changed. Its sometimes a private method
but its virtually always final. It calls other base-class methods (the ones
you override) in order to do its job, but it is usually called only as part of
an initialization process (and thus the client programmer isnt necessarily
able to call it directly).
//: c03:TemplateMethod.java
// Simple demonstration of Template Method.
import com.bruceeckel.test.*;
abstract class ApplicationFramework {
public ApplicationFramework() {
templateMethod(); // Dangerous!
}
abstract void customize1();
abstract void customize2();
// "private" means automatically "final":
private void templateMethod() {
for(int i = 0; i < 5; i++) {
customize1();
customize2();
}
}
}
// Create a new "application":
class MyApp extends ApplicationFramework {
void customize1() {
System.out.print("Hello ");
}
void customize2() {
System.out.println("World!");
}
}
Exercises
1.
4:Fronting for an
implementation
Both Proxy and State provide a surrogate class that you use in your code;
the real class that does the work is hidden behind this surrogate class.
When you call a method in the surrogate, it simply turns around and calls
the method in the implementing class. These two patterns are so similar
that the Proxy is simply a special case of State. One is tempted to just
lump the two together into a pattern called Surrogate, but the term
proxy has a long-standing and specialized meaning, which probably
explains the reason for the two different patterns.
The basic idea is simple: from a base class, the surrogate is derived along
with the class or classes that provide the actual implementation:
Interface
Surrogate
Implementation1
Surrogate
Implementation2
Etc.
Implementation
Proxy
If we implement Proxy by following the above diagram, it looks like this:
//: c04:ProxyDemo.java
// Simple demonstration of the Proxy pattern.
import com.bruceeckel.test.*;
interface ProxyBase {
void f();
void g();
void h();
}
Dynamic Proxies
In JDK 1.3, the Dynamic Proxy was introduced. Although a little complex
at first, this is an intruiging tool. Heres how it works
Exercise: Use the Java dynamic proxy to create an object that acts as a
front end for a simple configuration file. For example, in good_stuff.txt
you can have entries like this:
a=1
b=2
c="Hello World"
A client programmer of this NeatPropertyBundle could then write:
NeatPropertyBundle p =
new NeatPropertyBundle("good_stuff");
System.out.println(p.a);
System.out.println(p.b);
System.out.println(p.c);
The contents of the configuration file can contain anything, with any
variable names. The dynamic proxy will either map to the name or tell
you it doesnt exist somehow (probably by returning null). If you set a
property and it doesnt already exist, the dynamic proxy will create the
new entry. The toString( ) method should display all the current entries.
Exercise: similar to the previous exercise, use the Java dynamic proxy to
make a connection to the DOS Autoexec.bat file.
Exercise: Accept an SQL query which returns data, then read the DB
metadata. Now, for each record, provide an object which has attributes
corresponding to the column names and of appropriate data types.
Exercise: Create a simple server and client that uses XML-RPC. Each
object the client returns should use the dynamic proxy concept to exercise
the remote methods.
State
The State pattern adds more implementations to Proxy, along with a way
to switch from one implementation to another during the lifetime of the
surrogate:
//: c04:StateDemo.java
// Simple demonstration of the State pattern.
import com.bruceeckel.test.*;
interface StateBase {
void f();
void g();
void h();
}
class State_d implements StateBase {
private StateBase implementation;
public State_d(StateBase imp) {
implementation = imp;
}
public void changeImp(StateBase newImp) {
implementation = newImp;
}
// Pass method calls to the implementation:
public void f() { implementation.f(); }
public void g() { implementation.g(); }
public void h() { implementation.h(); }
}
class Implementation1 implements StateBase {
public void f() {
System.out.println("Implementation1.f()");
}
public void g() {
System.out.println("Implementation1.g()");
}
public void h() {
System.out.println("Implementation1.h()");
}
}
class Implementation2 implements StateBase {
public void f() {
System.out.println("Implementation2.f()");
}
public void g() {
System.out.println("Implementation2.g()");
}
public void h() {
System.out.println("Implementation2.h()");
}
}
public class StateDemo extends UnitTest {
static void run(State_d b) {
b.f();
b.g();
b.h();
}
State_d b = new State_d(new Implementation1());
public void test() {
// This just makes sure it will complete
// without throwing an exception.
run(b);
b.changeImp(new Implementation2());
run(b);
}
public static void main(String args[]) {
new StateDemo().test();
}
} ///:~
In main( ), you can see that the first implementation is used for a bit,
then the second implementation is swapped in and that is used.
The difference between Proxy and State is in the problems that are solved.
The common uses for Proxy as described in Design Patterns are:
1.
2.
3.
4.
StateMachine
While State has a way to allow the client programmer to change the
implementation, StateMachine imposes a structure to automatically
change the implementation from one object to the next. The current
implementation represents the state that a system is in, and the system
behaves differently from one state to the next (because it uses State).
Basically, this is a state machine using objects.
The code that moves the system from one state to the next is often a
Template Method, as seen in the following framework for a basic state
machine. We start by defining a tagging interface for input objects:
//: c04:statemachine:Input.java
// Inputs to a state machine
package c04.statemachine;
public interface Input {} ///:~
Each state can be run( ) to perform its behavior, and (in this design) you
can also pass it an Input object so it can tell you what new state to move
to based on that Input. The key distinction between this design and the
next is that here, each State object decides what other states it can move
to, based on the Input, whereas in the subsequent design all of the state
transitions are held in a single table. Another way to put it is that here,
each State object has its own little State table, and in the subsequent
design there is a single master state transition table for the whole system.
//: c04:statemachine:State.java
// A State has an operation, and can be moved
// into the next State given an Input:
package c04.statemachine;
public interface State {
void run();
State next(Input i);
} ///:~
The StateMachine keeps track of the current state, which is initialized
by the constructor. The runAll( ) method takes an Iterator to a list of
Input objects (an Iterator is used here for convenience and simplicity;
the important issue is that the input information comes from somewhere).
This method not only moves to the next state, but it also calls run( ) for
each state object thus you can see its an expansion of the idea of the
State pattern, since run( ) does something different depending on the
state that the system is in.
//: c04:statemachine:StateMachine.java
// This state machine takes an Iterator of Inputs
// to move from State to State using a template
// method.
package c04.statemachine;
import java.util.*;
public abstract class StateMachine {
private State currentState;
public StateMachine(State initialState) {
currentState = initialState;
currentState.run();
}
// Template method:
public final void
runAll(Iterator inputs) {
while(inputs.hasNext()) {
Input i = (Input)inputs.next();
System.out.println(i);
currentState = currentState.next(i);
currentState.run();
}
}
} ///:~
Ive also treated runAll( ) as a template method. This is typical, but
certainly not required you could concievably want to override it, but
typically the behavior change will occur in States run( ) instead.
At this point the basic framework for this style of StateMachine (where
each state decides the next states) is complete. As an example, Ill use a
fancy mousetrap that can move through several states in the process of
trapping a mouse1. The mouse classes and information are stored in the
mouse package, including a class representing all the possible moves
that a mouse can make, which will be the inputs to the state machine:
//: c04:mouse:MouseAction.java
// This state machine takes an Iterator of Inputs
// to move from State to State using a template
// method.
package c04.mouse;
import c04.statemachine.*;
public class MouseAction implements Input {
private String action;
public MouseAction(String a) { action = a; }
public String toString() { return action; }
public int hashCode() {
return action.hashCode();
}
public boolean equals(Object o) {
return (o instanceof MouseAction)
&& action.equals(((MouseAction)o).action);
}
public static MouseAction
appears = new MouseAction("mouse appears"),
runsAway = new MouseAction("mouse runs away"),
enters = new MouseAction("mouse enters trap"),
escapes = new MouseAction("mouse escapes"),
trapped = new MouseAction("mouse trapped"),
removed = new MouseAction("mouse removed");
} ///:~
Youll note that hashCode( ) and equals( ) have been overriden so that
MouseAction objects can be used as keys in a HashMap, but in the
first version of the mousetrap we wont do this. Also, each possible move
by a mouse is enumerated as a static MouseAction object.
For creating test code, a sequence of mouse inputs is provided from a text
file:
//:! c04:mouse:MouseMoves.txt
mouse appears
mouse runs away
mouse appears
mouse enters trap
mouse escapes
mouse appears
mouse enters trap
mouse trapped
mouse removed
mouse appears
mouse runs away
mouse appears
mouse enters trap
mouse trapped
mouse removed
///:~
To read this file in a generic fashion, here is a general-purpose tool called
StringList:
//: com:bruceeckel:util:StringList.java
// General-purpose tool that reads a file of text
// lines into a List, one line per list.
package com.bruceeckel.util;
import java.io.*;
import java.util.*;
public class StringList extends ArrayList {
public StringList(String textFile) {
try {
BufferedReader inputs =
new BufferedReader (
new FileReader(textFile));
String line;
while((line = inputs.readLine()) != null)
add(line.trim());
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
} ///:~
This StringList only holds Objects, just as an ArrayList does, so we
need an adapter to turn the Strings into MouseActions:
//: c04:mouse:MouseMoveList.java
// A transformer to produce a
// List of MouseAction objects.
package c04.mouse;
import java.util.*;
import com.bruceeckel.util.*;
public class MouseMoveList extends ArrayList {
public MouseMoveList(Iterator sit) {
while(sit.hasNext())
add(new MouseAction((String)sit.next()));
}
} ///:~
The MouseMoveList looks a bit like a decorator, and acts a bit like an
adapter. However, an adapter changes one interface to another, and a
decorator adds functionality or data. MouseMoveList changes the
contents of the container, so it might be thought of as a Transformer.
With these tools in place, its now possible to create the first version of the
mousetrap program. Each State subclass defines its run( ) behavior,
and also establishes its next state with an if-else clause:
//: c04:mousetrap1:MouseTrapTest.java
// State Machine pattern using 'if' statements
// to determine the next state.
package c04.mousetrap1;
import c04.mouse.*;
import c04.statemachine.*;
import com.bruceeckel.util.*;
import java.util.*;
import java.io.*;
import com.bruceeckel.test.*;
// A different subclass for each state:
class Waiting implements State {
public void run() {
System.out.println(
MouseAction ma = (MouseAction)i;
if(ma.equals(MouseAction.removed))
return MouseTrap.waiting;
return MouseTrap.holding;
}
}
class MouseTrap extends StateMachine {
public static State
waiting = new Waiting(),
luring = new Luring(),
trapping = new Trapping(),
holding = new Holding();
public MouseTrap() {
super(waiting); // Initial state
}
}
public class MouseTrapTest extends UnitTest {
MouseTrap trap = new MouseTrap();
MouseMoveList moves =
new MouseMoveList(
new StringList("../mouse/MouseMoves.txt")
.iterator());
public void test() {
trap.runAll(moves.iterator());
}
public static void main(String args[]) {
new MouseTrapTest().test();
}
} ///:~
The StateMachine class simply defines all the possible states as static
objects, and also sets up the initial state. The UnitTest creates a
MouseTrap and then tests it with all the inputs from a
MouseMoveList.
While the use of if-else statements inside the next( ) methods is
perfectly reasonable, managing a large number of these could become
difficult. Another approach is to create tables inside each State object
defining the various next states based on the input.
Initially, this seems like it ought to be quite simple. You should be able to
define a static table in each State subclass that defines the transitions in
terms of the other State objects. However, it turns out that this approach
{ MouseAction.trapped, MouseTrap.holding },
});
return super.next(i);
}
}
class Holding extends StateT {
public void run() {
System.out.println("Holding: Mouse caught");
}
public State next(Input i) {
if(transitions == null)
init(new Object[][] {
{ MouseAction.removed, MouseTrap.waiting },
});
return super.next(i);
}
}
public class MouseTrap2Test extends UnitTest {
MouseTrap trap = new MouseTrap();
MouseMoveList moves =
new MouseMoveList(
new StringList("../mouse/MouseMoves.txt")
.iterator());
public void test() {
trap.runAll(moves.iterator());
}
public static void main(String args[]) {
new MouseTrap2Test().test();
}
} ///:~
The rest of the code is identical the difference is in the next( ) methods
and the StateT class.
If you have to create and maintain a lot of State classes, this approach is
an improvement, since its easier to quickly read and understand the state
transitions from looking at the table.
Table-Driven State
Machine
The advantage of the previous design is that all the information about a
state, including the state transition information, is located within the
state class itself. This is generally a good design principle.
However, in a pure state machine, the machine can be completely
represented by a single state-transition table. This has the advantage of
locating all the information about the state machine in a single place,
which means that you can more easily create and maintain the table based
on a classic state-transition diagram.
The classic state-transition diagram uses a circle to represent each state,
and lines from the state pointing to all states that state can transition into.
Each transition line is annotated with conditions for transition and an
action during transition. Heres what it looks like:
(Simple State Machine Diagram)
Goals:
Reasonable implementation
Observations:
Similar to flyweight
Example:
Money Added/
Show Total
Enough Change/
Message Off
Money Added/
Show Total
Makes
Change
No Change
R e tur
Q uit /
n C ha
n
ge
Collecting
Quit/
Return Change
Unavailable
*
Quit/
Return Change
Quit/
Return Change
More?
Transition actions
If the Condition returns true, then the transition to a new state is made,
and as that transition is made some kind of action occurs (in the previous
state machine design, this was the run( ) method):
//: c04:statemachine2:Transition.java
// Transition function object for state machine
package c04.statemachine2;
public interface Transition {
void transition(Input i);
} ///:~
The table
With these classes in place, we can set up a 3-dimensional table where
each row completely describes a state. The first element in the row is the
current state, and the rest of the elements are each a row indicating what
the type of the input can be, the condition that must be satisfied in order
for this state change to be the correct one, the action that happens during
transition, and the new state to move into. Note that the Input object is
not just used for its type, it is also a Messenger object that carries
information to the Condition and Transition objects:
{ {CurrentState},
{Input, Condition(Input), Transition(Input), Next},
{Input, Condition(Input), Transition(Input), Next},
{Input, Condition(Input), Transition(Input), Next},
...
}
if(tran[2] != null)
((Transition)tran[2]).transition(input);
state = (State)tran[3];
return;
}
}
throw new RuntimeException(
"Input not supported for current state");
}
} ///:~
value = val;
}
public String toString() { return name; }
public int getValue() { return value; }
public final static SecondDigit
one = new SecondDigit("one", 0),
two = new SecondDigit("two", 1),
three = new SecondDigit("three", 2),
four = new SecondDigit("four", 3);
}
class ItemSlot {
int price;
int quantity;
static int counter = 0;
String id = Integer.toString(counter++);
public ItemSlot(int prc, int quant) {
price = prc;
quantity = quant;
}
public String toString() { return id; }
public int getPrice() { return price; }
public int getQuantity() { return quantity; }
public void decrQuantity() { quantity--; }
}
public class VendingMachine extends StateMachine{
StateMachine changeAvailable =
new ChangeAvailable();
int amount = 0;
FirstDigit first = null;
ItemSlot[][] items = new ItemSlot[4][4];
Condition notEnough = new Condition() {
public boolean condition(Input input) {
int i1 = first.getValue();
int i2 = ((SecondDigit)input).getValue();
return items[i1][i2].getPrice() > amount;
}
};
Condition itemAvailable = new Condition() {
public boolean condition(Input input) {
int i1 = first.getValue();
int i2 = ((SecondDigit)input).getValue();
return items[i1][i2].getQuantity() > 0;
}
};
{FirstDigit.class, null,
showDigit, VM.selecting}},
{ {VM.wantMore}, // Current state
// Input, test, transition, next state:
{Quit.quit, null,
returnChange, VM.quiescent},
{FirstDigit.class, null,
showDigit, VM.selecting}},
});
}
} ///:~
Tools
Another approach, as your state machine gets bigger, is to use an
automation tool whereby you configure a table and let the tool generate
the state machine code for you. This can be created yourself using a
language like Python, but there are also free, open-source tools such as
Libero, at http://www.imatix.com.
Exercises
1.
2.
3.
4.
5.
6.
7.
is a String naming the new state and the value is the new state
object. Inside each state subclass override a method nextState( )
that has its own state-transition table. The input to nextState( )
should be a single word that comes from a text file containing one
word per line.
8.
9.
10.
11.
12.
X: Decorators:
dynamic type
selection
The use of layered objects to dynamically and
transparently add responsibilities to individual objects is
referred to as the decorator pattern.
Used when subclassing creates too many (& inflexible) classes
All decorators that wrap around the original object must have the same
basic interface
Dynamic proxy/surrogate?
This accounts for the odd inheritance structure
Tradeoff: coding is more complicated when using decorators
Decoratable
Decorator1
Decorator2
operation()
addedState
operation()
addedBehavior()
operation()
A coffee example
Consider going down to the local coffee shop, BeanMeUp, for a coffee.
There are typically many different drinks on offer -- espressos, lattes, teas,
iced coffees, hot chocolate to name a few, as well as a number of extras
(which cost extra too) such as whipped cream or an extra shot of espresso.
You can also make certain changes to your drink at no extra cost, such as
asking for decaf coffee instead of regular coffee.
Quite clearly if we are going to model all these drinks and combinations,
there will be sizeable class diagrams. So for clarity we will only consider a
subset of the coffees: Espresso, Espresso Con Panna, Caf Late,
Cappuccino and Caf Mocha. We'll include 2 extras - whipped cream
("whipped") and an extra shot of espresso; and three changes - decaf,
steamed milk ("wet") and foamed milk ("dry").
menu is huge, and a part of the class diagram would look something like
this:
CoffeeShop
Espresso
DoubleEspresso
Cappuccino
CappuccinoDecaf
CappuccinoExtraEspresso
CafeMocha
CafeMochaDecaf
CafeMochaDecafWhipped
CafeMochaExtraEspressoWhipped
CafeLatteDecaf
CafeLatteExtraEspresso
CappuccinoWhipped
CappuccinoExtraEspressoWhipped
CafeMochaExtraEspresso
CafeLatte
CappuccinoDecafWhipped
CafeLatteDecafWhipped
CafeLatteExtraEspressoWhipped
CappuccinoDry
CappuccinoDryWhipped
CafeMochaWhipped
CafeMochaWet
CafeMochaWetWhipped
CafeLatteWhipped
CafeLatteWet
CafeLatteWetWhipped
class DoubleEspresso {}
class EspressoConPanna {}
class Cappuccino {
private float cost = 1;
private String description = "Cappucino";
public float getCost() {
return cost;
}
public String getDescription() {
return description;
}
}
class CappuccinoDecaf {}
class CappuccinoDecafWhipped {}
class CappuccinoDry {}
class CappuccinoDryWhipped {}
class CappuccinoExtraEspresso {}
class CappuccinoExtraEspressoWhipped {}
class CappuccinoWhipped {}
class CafeMocha {}
class CafeMochaDecaf {}
class CafeMochaDecafWhipped {
private float cost = 1.25f;
private String description =
"Cafe Mocha decaf whipped cream";
public float getCost() {
return cost;
}
public String getDescription() {
return description;
}
}
class CafeMochaExtraEspresso {}
class CafeMochaExtraEspressoWhipped {}
class CafeMochaWet {}
class CafeMochaWetWhipped {}
class CafeMochaWhipped {}
class
class
class
class
CafeLatte {}
CafeLatteDecaf {}
CafeLatteDecafWhipped {}
CafeLatteExtraEspresso {}
class
class
class
class
CafeLatteExtraEspressoWhipped {}
CafeLatteWet {}
CafeLatteWetWhipped {}
CafeLatteWhipped {}
CoffeeShop
Mug
Chocolate
Decaf
Decorator
Espresso
FoamedMilk
SteamedMilk
Whipped
}
You combine the components to create a drink as follows, as shown in the
code below:
//: cX:decorator:alldecorators:CoffeeShop.java
// Coffee example using decorators
package cX.decorator.alldecorators;
import com.bruceeckel.test.UnitTest;
interface DrinkComponent {
String getDescription();
float getTotalCost();
}
class Mug implements DrinkComponent {
public String getDescription() {
return "mug";
}
public float getTotalCost() {
return 0;
}
}
abstract class Decorator implements DrinkComponent
{
protected DrinkComponent component;
Decorator(DrinkComponent component) {
this.component = component;
}
public float getTotalCost() {
return component.getTotalCost();
}
public abstract String getDescription();
}
class Espresso extends Decorator {
private float cost = 0.75f;
private String description = " espresso";
public Espresso(DrinkComponent component) {
super(component);
}
public float getTotalCost() {
return component.getTotalCost() + cost;
}
public String getDescription() {
return component.getDescription() +
description;
}
}
class Decaf extends Decorator {
private String description = " decaf";
public Decaf(DrinkComponent component) {
super(component);
}
public String getDescription() {
return component.getDescription() +
description;
}
}
class FoamedMilk extends Decorator {
private float cost = 0.25f;
private String description = " foamed milk";
public FoamedMilk(DrinkComponent component) {
super(component);
}
public float getTotalCost() {
return component.getTotalCost() + cost;
}
public String getDescription() {
return component.getDescription() +
description;
}
}
class SteamedMilk extends Decorator {
private float cost = 0.25f;
private String description = " steamed milk";
public SteamedMilk(DrinkComponent component) {
super(component);
}
public float getTotalCost() {
return component.getTotalCost() + cost;
}
public String getDescription() {
return component.getDescription() +
description;
}
}
class Whipped extends Decorator {
private float cost = 0.25f;
private String description = " whipped cream";
public Whipped(DrinkComponent component) {
super(component);
}
public float getTotalCost() {
return component.getTotalCost() + cost;
}
public String getDescription() {
return component.getDescription() +
description;
}
}
class Chocolate extends Decorator {
private float cost = 0.25f;
private String description = " chocolate";
public Chocolate(DrinkComponent component) {
super(component);
}
public float getTotalCost() {
return component.getTotalCost() + cost;
}
public String getDescription() {
return component.getDescription() +
description;
}
}
public class CoffeeShop extends UnitTest {
public void testCappuccino() {
// This just makes sure it will complete
// without throwing an exception.
// Create a plain cappucino
DrinkComponent cappuccino = new Espresso(
new FoamedMilk(new Mug()));
System.out.println(cappuccino.
getDescription().trim() + ": $" +
cappuccino.getTotalCost());
}
public void testCafeMocha() {
Compromise
The previous approach takes too long to describe a coffee. There will also
be certain combinations that you will describe regularly, and it would be
convenient to have a quick way of describing them.
The 3rd approach is a mixture of the first 2 approaches, and combines
flexibility with ease of use. This compromise is achieved by creating a
reasonably sized menu of basic selections, which would often work exactly
as they are, but if you wanted to decorate them (whipped cream, decaf
etc.) then you would use decorators to make the modifications. This is the
type of menu you are presented with in most coffee shops.
CoffeeShop
nterface
DrinkComponent
CafeMocha
Espresso
CafeLatte
Cappuccino
Decorator
Decaf
Dry
ExtraEspresso
Wet
Whipped
return description;
}
}
class Cappuccino implements DrinkComponent {
private float cost = 1;
private String description = "Cappuccino";
public float getTotalCost() {
return cost;
}
public String getDescription() {
return description;
}
}
class CafeLatte implements DrinkComponent {
private float cost = 1;
private String description = "Cafe Late";
public float getTotalCost() {
return cost;
}
public String getDescription() {
return description;
}
}
class CafeMocha implements DrinkComponent {
private float cost = 1.25f;
private String description = "Cafe Mocha";
public float getTotalCost() {
return cost;
}
public String getDescription() {
return description;
}
}
abstract class Decorator implements DrinkComponent {
protected DrinkComponent component;
public Decorator(DrinkComponent component) {
this.component = component;
}
public float getTotalCost() {
return component.getTotalCost();
}
public String getDescription() {
return component.getDescription();
}
}
class ExtraEspresso extends Decorator {
private float cost = 0.75f;
public ExtraEspresso(DrinkComponent component) {
super(component);
}
public String getDescription() {
return component.getDescription() +
" extra espresso";
}
public float getTotalCost() {
return cost + component.getTotalCost();
}
}
class Whipped extends Decorator {
private float cost = 0.50f;
public Whipped(DrinkComponent component) {
super(component);
}
public float getTotalCost() {
return cost + component.getTotalCost();
}
public String getDescription() {
return component.getDescription() +
" whipped cream";
}
}
class Decaf extends Decorator{
public Decaf(DrinkComponent component) {
super(component);
}
public String getDescription() {
return component.getDescription() + " decaf";
}
}
class Dry extends Decorator {
You can see that creating a basic selection is quick and easy, which makes
sense since they will be described regularly. Describing a decorated drink
is more work than when using a class per combination, but clearly less
work than when only using decorators.
The final result is not too many classes, but not too many decorators
either. Most of the time it's possible to get away without using any
decorators at all, so we have the benefits of both approaches.
Other considerations
What happens if we decide to change the menu at a later stage, such as by
adding a new type of drink? If we had used the class per combination
approach, the effect of adding an extra such as syrup would be an
exponential growth in the number of classes. However, the implications
to the all decorator or compromise approaches are the same - one extra
class is created.
How about the effect of changing the cost of steamed milk and foamed
milk, when the price of milk goes up? Having a class for each combination
means that you need to change a method in each class, and thus maintain
many classes. By using decorators, maintenance is reduced by defining
the logic in one place.
Exercises
1.
2.
3.
Y: Iterators:
decoupling
algorithms from
containers
Alexander Stepanov thought for years about the problem
of generic programming techniques before creating the
STL (along with Dave Musser). He came to the conclusion
that all algorithms are defined on algebraic structures
what we would call containers.
In the process, he realized that iterators are central to the use of
algorithms, because they decouple the algorithms from the specific type
of container that the algorithm might currently be working with. This
means that you can describe the algorithm without worrying about the
particular sequence it is operating on. More generally, any code that you
write using iterators is decoupled from the data structure that the code is
manipulating, and thus your code is more general and reusable.
The use of iterators also extends your code into the realm of functional
programming, whose objective is to describe what a program is doing at
every step rather than how it is doing it. That is, you say sort rather
than describing the sort. The objective of the C++ STL was to provide this
generic programming approach for C++ (how successful this approach
will actually be remains to be seen).
If youve used containers in Java (and its hard to write code without
using them), youve used iterators in the form of the Enumeration in
Java 1.0/1.1 and the Iterator in Java 2. So you should already be familiar
with their general use. If not, see Chapter 9, Holding Your Objects, under
Type-safe iterators
In Thinking in Java, 2nd edition, I show the creation of a type-safe
container that will only accept a particular type of object. A reader, Linda
Pazzaglia, asked for the other obvious type-safe component, an iterator
that would work with the basic java.util containers, but impose the
constraint that the type of objects that it iterates over be of a particular
type.
If Java ever includes a template mechanism, this kind of iterator will have
the added advantage of being able to return a specific type of object, but
without templates you are forced to return generic Objects, or to require
a bit of hand-coding for every type that you want to iterate through. I will
take the former approach.
A second design decision involves the time that the type of object is
determined. One approach is to take the type of the first object that the
iterator encounters, but this is problematic because the containers may
rearrange the objects according to an internal ordering mechanism (such
as a hash table) and thus you may get different results from one iteration
to the next. The safe approach is to require the user to establish the type
during construction of the iterator.
Lastly, how do we build the iterator? We cannot rewrite the existing Java
library classes that already produce Enumerations and Iterators.
However, we can use the Decorator design pattern, and create a class that
simply wraps the Enumeration or Iterator that is produced,
generating a new object that has the iteration behavior that we want
(which is, in this case, to throw a RuntimeException if an incorrect
type is encountered) but with the same interface as the original
Enumeration or Iterator, so that it can be used in the same places
(you may argue that this is actually a Proxy pattern, but its more likely
Decorator because of its intent). Here is the code:
//: com:bruceeckel:util:TypedIterator.java
package com.bruceeckel.util;
import java.util.*;
public class TypedIterator implements Iterator {
private Iterator imp;
private Class type;
public TypedIterator(Iterator it, Class type) {
imp = it;
this.type = type;
}
public boolean hasNext() {
return imp.hasNext();
}
public void remove() { imp.remove(); }
public Object next() {
Object obj = imp.next();
if(!type.isInstance(obj))
throw new ClassCastException(
"TypedIterator for type " + type +
" encountered type: " + obj.getClass());
return obj;
}
} ///:~
5: Factories:
encapsulating
object creation
When you discover that you need to add new types to a system, the most
sensible first step is to use polymorphism to create a common interface to
those new types. This separates the rest of the code in your system from
the knowledge of the specific types that you are adding. New types may be
added without disturbing existing code or so it seems. At first it would
appear that the only place you need to change the code in such a design is
the place where you inherit a new type, but this is not quite true. You
must still create an object of your new type, and at the point of creation
you must specify the exact constructor to use. Thus, if the code that
creates objects is distributed throughout your application, you have the
same problem when adding new typesyou must still chase down all the
points of your code where type matters. It happens to be the creation of
the type that matters in this case rather than the use of the type (which is
taken care of by polymorphism), but the effect is the same: adding a new
type can cause problems.
The solution is to force the creation of objects to occur through a common
factory rather than to allow the creational code to be spread throughout
your system. If all the code in your program must go through this factory
whenever it needs to create one of your objects, then all you must do
when you add a new object is to modify the factory.
Since every object-oriented program creates objects, and since its very
likely you will extend your program by adding new types, I suspect that
factories may be the most universally useful kinds of design patterns.
System.out.println("Circle.draw");
}
public void erase() {
System.out.println("Circle.erase");
}
}
class Square extends Shape {
Square() {} // Friendly constructor
public void draw() {
System.out.println("Square.draw");
}
public void erase() {
System.out.println("Square.erase");
}
}
public class ShapeFactory1 extends UnitTest {
String shlist[] = { "Circle", "Square",
"Square", "Circle", "Circle", "Square" };
List shapes = new ArrayList();
public void test() {
for(int i = 0; i < shlist.length; i++)
shapes.add(Shape.factory(shlist[i]));
Iterator i = shapes.iterator();
while(i.hasNext()) {
Shape s = (Shape)i.next();
s.draw();
s.erase();
}
}
public static void main(String args[]) {
new ShapeFactory1().test();
}
} ///:~
The factory( ) takes an argument that allows it to determine what type of
Shape to create; it happens to be a String in this case but it could be any
set of data. The factory( ) is now the only other code in the system that
needs to be changed when a new type of Shape is added (the
initialization data for the objects will presumably come from somewhere
outside the system, and not be a hard-coded array as in the above
example).
Polymorphic factories
The static factory( ) method in the previous example forces all the
creation operations to be focused in one spot, so thats the only place you
need to change the code. This is certainly a reasonable solution, as it
throws a box around the process of creating objects. However, the Design
Patterns book emphasizes that the reason for the Factory Method pattern
is so that different types of factories can be subclassed from the basic
factory (the above design is mentioned as a special case). However, the
book does not provide an example, but instead just repeats the example
used for the Abstract Factory (youll see an example of this in the next
section). Here is ShapeFactory1.java modified so the factory methods
are in a separate class as virtual functions. Notice also that the specific
Shape classes are dynamically loaded on demand:
//: c05:shapefact2:ShapeFactory2.java
// Polymorphic factory methods.
package c05.shapefact2;
import java.util.*;
import com.bruceeckel.test.*;
interface Shape {
void draw();
void erase();
}
abstract class ShapeFactory {
protected abstract Shape create();
private static Map factories = new HashMap();
public static void
addFactory(String id, ShapeFactory f) {
factories.put(id, f);
}
// A Template Method:
public static final
Shape createShape(String id) {
if(!factories.containsKey(id)) {
try {
// Load dynamically
Class.forName("c05.shapefact2." + id);
} catch(ClassNotFoundException e) {
throw new RuntimeException(
"Bad shape creation: " + id);
}
// See if it was put in:
if(!factories.containsKey(id))
throw new RuntimeException(
"Bad shape creation: " + id);
}
return
((ShapeFactory)factories.get(id)).create();
}
}
class Circle implements Shape {
private Circle() {}
public void draw() {
System.out.println("Circle.draw");
}
public void erase() {
System.out.println("Circle.erase");
}
private static class Factory
extends ShapeFactory {
protected Shape create() {
return new Circle();
}
}
static {
ShapeFactory.addFactory(
"Circle", new Factory());
}
}
class Square implements Shape {
private Square() {}
public void draw() {
System.out.println("Square.draw");
}
public void erase() {
System.out.println("Square.erase");
}
private static class Factory
extends ShapeFactory {
protected Shape create() {
return new Square();
}
}
static {
ShapeFactory.addFactory(
"Square", new Factory());
}
}
public class ShapeFactory2 extends UnitTest {
String shlist[] = { "Circle", "Square",
"Square", "Circle", "Circle", "Square" };
List shapes = new ArrayList();
public void test() {
// This just makes sure it will complete
// without throwing an exception.
for(int i = 0; i < shlist.length; i++)
shapes.add(
ShapeFactory.createShape(shlist[i]));
Iterator i = shapes.iterator();
while(i.hasNext()) {
Shape s = (Shape)i.next();
s.draw();
s.erase();
}
}
public static void main(String args[]) {
new ShapeFactory2().test();
}
} ///:~
Now the factory method appears in its own class, ShapeFactory, as the
create( ) method. This is a protected method which means it cannot be
called directly, but it can be overridden. The subclasses of Shape must
each create their own subclasses of ShapeFactory and override the
create( ) method to create an object of their own type. The actual
creation of shapes is performed by calling
ShapeFactory.createShape( ), which is a static method that uses the
Map in ShapeFactory to find the appropriate factory object based on
an identifier that you pass it. The factory is immediately used to create the
shape object, but you could imagine a more complex problem where the
appropriate factory object is returned and then used by the caller to
create an object in a more sophisticated way. However, it seems that
much of the time you dont need the intricacies of the polymorphic
factory method, and a single static method in the base class (as shown in
ShapeFactory1.java) will work fine.
Notice that the ShapeFactory must be initialized by loading its Map
with factory objects, which takes place in the static initialization clause of
each of the Shape implementations. So to add a new type to this design
you must inherit the type, create a factory, and add the static initialization
clause to load the Map. This extra complexity again suggests the use of a
static factory method if you dont need to create individual factory
objects.
Abstract factories
The Abstract Factory pattern looks like the factory objects weve seen
previously, with not one but several factory methods. Each of the factory
methods creates a different kind of object. The idea is that at the point of
creation of the factory object, you decide how all the objects created by
that factory will be used. The example given in Design Patterns
implements portability across various graphical user interfaces (GUIs):
you create a factory object appropriate to the GUI that youre working
with, and from then on when you ask it for a menu, button, slider, etc. it
will automatically create the appropriate version of that item for the GUI.
Thus youre able to isolate, in one place, the effect of changing from one
GUI to another.
As another example suppose you are creating a general-purpose gaming
environment and you want to be able to support different types of games.
Heres how it might look using an abstract factory:
//: c05:Games.java
// An example of the Abstract Factory pattern.
import com.bruceeckel.test.*;
interface Obstacle {
void action();
}
interface Player {
void interactWith(Obstacle o);
}
class Kitty implements Player {
Exercises
1.
2.
3.
4.
6: Function objects
In Advanced C++:Programming Styles And Idioms (Addison-Wesley,
1992), Jim Coplien coins the term functor which is an object whose sole
purpose is to encapsulate a function (since functor has a meaning in
mathematics, in this book I shall use the more explicit term function
object). The point is to decouple the choice of function to be called from
the site where that function is called.
This term is mentioned but not used in Design Patterns. However, the
theme of the function object is repeated in a number of patterns in that
book.
In the Python language, all functions are already objects and so the Command
pattern is often redundant.
Page 235.
} ///:~
Note similarity with template method TM claims distinction that it has
more than one method to call, does things piecewise. However, its not
unlikely that strategy object would have more than one method call;
consider Shalloways order fulfullment system with country information
in each strategy.
Strategy example from JDK: comparator objects.
Chain of responsibility
Chain of Responsibility might be thought of as a dynamic generalization
of recursion using Strategy objects. You make a call, and each Strategy in
a linked sequence tries to satisfy the call. The process ends when one of
the strategies is successful or the chain ends. In recursion, one method
calls itself over and over until a termination condition is reached; with
Chain of Responsibility, a method calls itself, which (by moving down the
chain of Strategies) calls a different implementation of the method, etc.,
until a termination condition is reached. The termination condition is
either the bottom of the chain is reached (in which case a default object is
returned; you may or may not be able to provide a default result so you
must be able to determine the success or failure of the chain) or one of the
Strategies is successful.
Instead of calling a single method to satisfy a request, multiple methods
in the chain have a chance to satisfy the request, so it has the flavor of an
expert system. Since the chain is effectively a linked list, it can be
dynamically created, so you could also think of it as a more general,
dynamically-built switch statement.
In the GoF, theres a fair amount of discussion of the implementation
details of the chain of responsibility as a linked list. However, when you
look at the pattern it really shouldnt matter how the chain is maintained;
thats an implementation detail. Since GoF was written before the
Standard Template Library (STL) was incorporated into most C++
compilers, the reason for this is most likely (1) there was no list and thus
they had to create one and (2) data structures are often taught as a
fundamental skill in academia, and the idea that data structures should be
standard tools available with the programming language may not have
occurred to the GoF authors. I maintain that the implementation of Chain
of Responsibility as a chain (specifically, a linked list) adds nothing to the
solution and can just as easily be implemented using a standard Java List,
as shown below. Furthermore, youll see that Ive gone to some effort to
separate the chain-management parts of the implementation from the
various Strategies, so that the code can be more easily reused.
In StrategyPattern.java, above, what you probably want is to
automatically find a solution. Chain of Responsibility provides a way to
do this by chaining the Strategy objects together and providing a
mechanism for them to automatically recurse through each one in the
chain:
//: c06:ChainOfResponsibility.java
import com.bruceeckel.util.*; // Arrays2.print()
import com.bruceeckel.test.*;
import java.util.*;
// Carry the information into the strategy:
interface Messenger {}
// The Result object carries the result data and
// whether the strategy was successful:
class Result {
private boolean succeeded;
public boolean isSuccessful() {
return succeeded;
}
public void setSuccessful(boolean b) {
succeeded = b;
}
}
abstract class Strategy {
abstract public Result strategy(Messenger m);
}
// Manage the movement through the chain and
// find a successful result:
class ChainLink {
private List chain;
private Strategy strat;
public ChainLink(List chain, Strategy s) {
strat = s;
this.chain = chain;
chain.add(this);
}
public ChainLink next() {
Exercises
1.
2.
7: Changing the
interface
Sometimes the problem that youre solving is as simple as I dont have
the interface that I want. Two of the patterns in Design Patterns solve
this problem: Adapter takes one type and produces an interface to some
other type. Faade creates an interface to a set of classes, simply to
Adapter
When youve got this, and you need that, Adapter solves the problem. The
only requirement is to produce a that, and there are a number of ways you
can accomplish this adaptation.
//: c07:Adapter.java
// Variations on the Adapter pattern.
import com.bruceeckel.test.*;
class WhatIHave {
public void g() {}
public void h() {}
}
interface WhatIWant {
void f();
}
class ProxyAdapter implements WhatIWant {
WhatIHave whatIHave;
public ProxyAdapter(WhatIHave wih) {
whatIHave = wih;
}
public void f() {
// Implement behavior using
// methods in WhatIHave:
whatIHave.g();
whatIHave.h();
}
}
class WhatIUse {
public void op(WhatIWant wiw) {
wiw.f();
}
}
// Approach 2: build adapter use into op():
class WhatIUse2 extends WhatIUse {
new Adapter().test();
}
} ///:~
Im taking liberties with the term proxy here, because in Design
Patterns they assert that a proxy must have an identical interface with the
object that it is a surrogate for. However, if you have the two words
together: proxy adapter, it is perhaps more reasonable.
Faade
A general principle that I apply when Im casting about trying to mold
requirements into a first-cut object is If something is ugly, hide it inside
an object. This is basically what Faade accomplishes. If you have a
rather confusing collection of classes and interactions that the client
programmer doesnt really need to see, then you can create an interface
that is useful for the client programmer and that only presents whats
necessary.
Faade is often implemented as singleton abstract factory. Of course, you
can easily get this effect by creating a class containing static factory
methods:
//: c07:Facade.java
import com.bruceeckel.test.*;
class A { public A(int x) {} }
class B { public B(long x) {} }
class C { public C(double x) {} }
// Other classes that aren't exposed by the
// facade go here ...
public class Facade extends UnitTest {
static A makeA(int x) { return new A(x); }
static B makeB(long x) { return new B(x); }
static C makeC(double x) { return new C(x); }
// The client programmer gets the objects
// by calling the static methods:
A a = Facade.makeA(1);
B b = Facade.makeB(1);
C c = Facade.makeC(1.0);
public void test() {}
public static void main(String args[]) {
new Facade().test();
}
} ///:~
The example given in Design Patterns isnt really a Faade but just a class
that uses the other classes.
Exercises
1.
The java.util.Map has no way to automatically load a twodimensional array of objects into a Map as key-value pairs. Create
an adapter class that does this.
8: Table-driven
code:
configuration
flexibility
Table-driven code using
anonymous inner
classes
See ListPerformance.java example in TIJ from Chapter 9
Also GreenHouse.java
9: Interpreter/
Multiple
Languages
This chapter looks at the value of crossing language boundaries. It is often
very advantageous to solve a problem using more than one programming
Interpreter motivation
If the application user needs greater run time flexibility, for example to
create scripts describing the desired behavior of the system, you can use
the Interpreter design pattern. Here, you create and embed a language
interpreter into your program.
Remember that each design pattern allows one or more factors to change,
so its important to first be aware of which factor is changing. Sometimes
the end users of your application (rather than the programmers of that
application) need complete flexibility in the way that they configure some
aspect of the program. That is, they need to do some kind of simple
programming. The interpreter pattern provides this flexibility by adding a
language interpreter.
The problem is that developing your own language and building an
interpreter is a time-consuming distraction from the process of
developing your application. You must ask whether you want to finish
writing your application or create a new language. The best solution is to
The original version of this was called JPython, but the project changed and the
name was changed to emphasize the distinctness of the new version.
1
reuse code: embed an interpreter thats already been built and debugged
for you. The Python language can be freely embedded into your for-profit
application without signing any license agreement, paying royalties, or
dealing with strings of any kind. There are basically no restrictions at all
when you're using Python.
Python is a language that is very easy to learn, very logical to read and
write, supports functions and objects, has a large set of available libraries,
and runs on virtually every platform. You can download Python and learn
more about it by going to www.Python.org.
For solving Java problems, we will look at a special version of Python
called Jython. This is generated entirely in Java byte codes, so
incorporating it into your application is quite simple, and its as portable
as Java is. It has an extremely clean interface with Java: Java can call
Python classes, and Python can call Java classes.
Python is designed with classes from the ground up and is a truly pure
object oriented language (both C++ and Java violate purity in various
ways). Python scales up so that you can create very big programs without
losing control of the code.
To install Python, go to www.Python.org and follow the links and
instructions. To install Jython, go to http://jython.sourceforge.net. The
download is a .class file, which will run an installer when you execute it
with Java. You also need to add jython.jar to your CLASSPATH. You
can find further installation instructions at
http://www.bruceeckel.com/TIPatterns/Building-Code.html.
Python overview
To get you started, here is a brief introduction for the experienced
programmer (which is what you should be if youre reading this book).
You can refer to the full documentation at www.Python.org (especially
the incredibly useful HTML page A Python Quick Reference), and also
numerous books such as Learning Python by Mark Lutz and David
Ascher (OReilly, 1999).
Python is often referred to as a scripting language, but scripting
languages tend to be limiting, especially in the scope of the problems that
they solve. Python, on the other hand, is a programming language that
also supports scripting. It is marvelous for scripting, and you may find
yourself replacing all your batch files, shell scripts, and simple programs
with Python scripts. But it is far more than a scripting language.
Python is designed to be very clean to write and especially to read. You
will find that its quite easy to read your own code long after youve
written it, and also to read other peoples code. This is accomplished
partially through clean, to-the-point syntax, but a major factor in code
readability is indentation scoping in Python is determined by
indentation. For example:
#: c09:if.py
response = "yes"
if response == "yes":
print "affirmative"
val = 1
print "continuing..."
#:~
The # denotes a comment that goes until the end of the line, just like
C++ and Java // comments.
First notice that the basic syntax of Python is C-ish; notice the if
statement. But in a C if, you would be required to use parentheses around
the conditional, whereas they are not necessary in Python (but it wont
complain if you use them anyway).
The conditional clause ends with a colon, and this indicates that what
follows will be a group of indented statements, which are the then part
of the if statement. In this case, there is a print statement which sends
the result to standard output, followed by an assignment to a variable
named val. The subsequent statement is not indented so it is no longer
part of the if. Indenting can nest to any level, just like curly braces in C++
or Java, but unlike those languages there is no option (and no argument)
about where the braces are placed the compiler forces everyones code
to be formatted the same way, which is one of the main reasons for
Pythons consistent readability.
Python normally has only one statement per line (you can put more by
separating them with semicolons), thus no terminating semicolon is
necessary. Even from the brief example above you can see that the
language is designed to be as simple as possible, and yet still very
readable.
Built-in containers
With languages like C++ and Java, containers are add-on libraries and
not integral to the language. In Python, the essential nature of containers
for programming is acknowledged by building them into the core of the
language: both lists and associative arrays (a.k.a. maps, dictionaries, hash
tables) are fundamental data types. This adds much to the elegance of the
language.
In addition, the for statement automatically iterates through lists rather
than just counting through a sequence of numbers. This makes a lot of
sense when you think about it, since youre almost always using a for
loop to step through an array or a container. Python formalizes this by
automatically making for use an iterator that works through a sequence.
Heres an example:
#: c09:list.py
list = [ 1, 3, 5, 7, 9, 11 ]
print list
list.append(13)
for x in list:
print x
#:~
The first line creates a list. You can print the list and it will look exactly as
you put it in (in contrast, remember that I had to create a special
Arrays2 class in Thinking in Java, 2nd Edition in order to print arrays in
Java). Lists are like Java containers you can add new elements to them
(here, append( ) is used) and they will automatically resize themselves.
The for statement creates an iterator x which takes on each value in the
list.
You can create a list of numbers with the range( ) function, so if you
really need to imitate Cs for, you can.
Notice that there arent any type declarations the object names simply
appear, and Python infers their type by the way that you use them. Its as
if Python is designed so that you only need to press the keys that
absolutely must. Youll find after youve worked with Python for a short
while that youve been using up a lot of brain cycles parsing semicolons,
curly braces, and all sorts of other extra verbiage that was demanded by
your non-Python programming language but didnt actually describe
what your program was supposed to do.
Functions
To create a function in Python, you use the def keyword, followed by the
function name and argument list, and a colon to begin the function body.
Here is the first example turned into a function:
#: c09:myFunction.py
def myFunction(response):
val = 0
if response == "yes":
print "affirmative"
val = 1
print "continuing..."
return val
print myFunction("no")
print myFunction("yes")
#:~
Notice there is no type information in the function signature all it
specifies is the name of the function and the argument identifiers, but no
argument types or return types. Python is a weakly-typed language,
which means it puts the minimum possible requirements on typing. For
example, you could pass and return different types from the same
function:
#: c09:differentReturns.py
def differentReturns(arg):
if arg == 1:
return "one"
if arg == "one":
return 1
print differentReturns(1)
print differentReturns("one")
#:~
The only constraints on an object that is passed into the function are that
the function can apply its operations to that object, but other than that, it
doesnt care. Here, the same function applies the + operator to integers
and strings:
#: c09:sum.py
def sum(arg1, arg2):
return arg1 + arg2
Strings
The above example also shows a little bit about Python string handling,
which is the best of any language Ive seen. You can use single or double
quotes to represent strings, which is very nice because if you surround a
string with double quotes, you can embed single quotes and vice versa:
#: c09:strings.py
print "That isn't a horse"
print 'You are not a "Viking"'
print """You're just pounding two
coconut halves together."""
print '''"Oh no!" He exclaimed.
"It's the blemange!"'''
print r'c:\python\lib\utils'
#:~
Note that Python was not named after the snake, but rather the Monty
Python comedy troupe, and so examples are virtually required to include
Python-esque references.
The triple-quote syntax quotes everything, including newlines. This
makes it particularly useful for doing things like generating web pages
(Python is an especially good CGI language), since you can just triplequote the entire page that you want without any other editing.
The r right before a string means raw, which takes the backslashes
literally so you dont have to put in an extra backslash.
Substitution in strings is exceptionally easy, since Python uses Cs
printf( ) substitution syntax, but for any string at all. You simply follow
the string with a % and the values to substitute:
#: c09:stringFormatting.py
val = 47
print "The number is %d" % val
val2 = 63.4
s = "val: %d, val2: %f" % (val, val2)
print s
#:~
As you can see in the second case, if you have more than one argument
you surround them in parentheses (this forms a tuple, which is a list that
cannot be modified).
All the formatting from printf( ) is available, including control over the
number of decimal places and alignment. Python also has very
sophisticated regular expressions.
Classes
Like everything else in Python, the definition of a class uses a minimum of
additional syntax. You use the class keyword, and inside the body you
use def to create methods. Heres a simple class:
#: c09:SimpleClass.py
class Simple:
def __init__(self, str):
print "Inside the Simple constructor"
self.s = str
# Two methods:
def show(self):
print self.s
def showMsg(self, msg):
print msg + ':',
self.show() # Calling another method
if __name__ == "__main__":
# Create an object:
x = Simple("constructor argument")
x.show()
x.showMsg("A message")
#:~
Both methods have self as their first argument. C++ and Java both
have a hidden first argument in their class methods, which points to the
object that the method was called for and can be accessed using the
keyword this. Python methods also use a reference to the current object,
but when you are defining a method you must explicitly specify the
reference as the first argument. Traditionally, the reference is called self
but you could use any identifier you want (if you do not use self you will
probably confuse a lot of people, however). If you need to refer to fields in
the object or other methods in the object, you must use self in the
expression. However, when you call a method for an object as in
x.show( ), you do not hand it the reference to the object that is done
for you.
Here, the first method is special, as is any identifier that begins and ends
with double underscores. In this case, it defines the constructor, which is
automatically called when the object is created, just like in C++ and Java.
However, at the bottom of the example you can see that the creation of an
object looks just like a function call using the class name. Pythons spare
syntax makes you realize that the new keyword isnt really necessary in
C++ or Java, either.
All the code at the bottom is set off by an if clause, which checks to see if
something called __name__ is equivalent to __main__. Again, the
double underscores indicate special names. The reason for the if is that
any file can also be used as a library module within another program
(modules are described shortly). In that case, you just want the classes
defined, but you dont want the code at the bottom of the file to be
executed. This particular if statement is only true when you are running
this file directly; that is, if you say on the command line:
Python SimpleClass.py
However, if this file is imported as a module into another program, the
__main__ code is not executed.
Something thats a little surprising at first is that you define fields inside
methods, and not outside of the methods like C++ or Java (if you create
fields using the C++/Java style, they implicitly become static fields). To
create an object field, you just name it using self inside of one of the
methods (usually in the constructor, but not always), and space is created
when that method is run. This seems a little strange coming from C++ or
Java where you must decide ahead of time how much space your object is
going to occupy, but it turns out to be a very flexible way to program.
Inheritance
Because Python is weakly typed, it doesnt really care about interfaces
all it cares about is applying operations to objects (in fact, Javas
interface keyword would be wasted in Python). This means that
inheritance in Python is different from inheritance in C++ or Java, where
you often inherit simply to establish a common interface. In Python, the
only reason you inherit is to inherit an implementation to re-use the
code in the base class.
If youre going to inherit from a class, you must tell Python to bring that
class into your new file. Python controls its name spaces as aggressively as
Java does, and in a similar fashion (albeit with Pythons penchant for
simplicity). Every time you create a file, you implicitly create a module
(which is like a package in Java) with the same name as that file. Thus, no
package keyword is needed in Python. When you want to use a module,
you just say import and give the name of the module. Python searches
the PYTHONPATH in the same way that Java searches the CLASSPATH
(but for some reason, Python doesnt have the same kinds of pitfalls as
Java does) and reads in the file. To refer to any of the functions or classes
within a module, you give the module name, a period, and the function or
class name. If you dont want the trouble of qualifying the name, you can
say
from module import name(s)
Where name(s) can be a list of names separated by commas.
You inherit a class (or classes Python supports multiple inheritance) by
listing the name(s) of the class inside parentheses after the name of the
inheriting class. Note that the Simple class, which resides in the file (and
thus, module) named SimpleClass is brought into this new name space
using an import statement:
#: c09:Simple2.py
from SimpleClass import Simple
class Simple2(Simple):
def __init__(self, str):
print "Inside Simple2 constructor"
# You must explicitly call
# the base-class constructor:
Simple.__init__(self, str)
def display(self):
self.showMsg("Called from display()")
# Overriding a base-class method
def show(self):
print "Overridden show() method"
# Calling a base-class method from inside
# the overridden method:
Simple.show(self)
class Different:
def show(self):
Creating a language
It turns out to be remarkably simple to use Jython to create an
interpreted language inside your application. Consider the greenhouse
controller example from Chapter 8 of Thinking in Java, 2nd edition. This
is a situation where you want the end user the person managing the
greenhouse to have configuration control over the system, and so a
simple scripting language is the ideal solution.
To create the language, well simply write a set of Python classes, and the
constructor of each will add itself to a (static) master list. The common
data and behavior will be factored into the base class Event. Each Event
object will contain an action string (for simplicity in reality, youd have
some sort of functionality) and a time when the event is supposed to run.
The constructor initializes these fields, and then adds the new Event
object to a static list called events (defining it in the class, but outside of
any methods, is what makes it static):
#:c09:GreenHouseLanguage.py
class Event:
events = [] # static
def __init__(self, action, time):
self.action = action
self.time = time
Event.events.append(self)
# Used by sort(). This will cause
# comparisons to be based only on time:
def __cmp__ (self, other):
if self.time < other.time: return -1
if self.time > other.time: return 1
return 0
def run(self):
print "%.2f: %s" % (self.time, self.action)
class LightOn(Event):
def __init__(self, time):
Event.__init__(self, "Light on", time)
class LightOff(Event):
def __init__(self, time):
Event.__init__(self, "Light off", time)
class WaterOn(Event):
def __init__(self, time):
Event.__init__(self, "Water on", time)
class WaterOff(Event):
def __init__(self, time):
Event.__init__(self, "Water off", time)
class ThermostatNight(Event):
def __init__(self, time):
import com.bruceeckel.test.*;
public class
GreenHouseController extends UnitTest {
PythonInterpreter interp =
new PythonInterpreter();
public void test() throws PyException {
System.out.println(
"Loading GreenHouse Language");
interp.execfile("GreenHouseLanguage.py");
System.out.println(
"Loading GreenHouse Script");
interp.execfile("Schedule.ghs");
System.out.println(
"Executing GreenHouse Script");
interp.exec("run()");
}
public static void
main(String[] args) throws PyException {
new GreenHouseController().test();
}
} ///:~
The PythonInterpreter object is a complete Python interpreter that
accepts commands from the Java program. One of these commands is
execfile( ), which tells it to execute all the statements it finds in a
particular file. By executing GreenHouseLanguage.py, all the classes
from that file are loaded into our PythonInterpreter object, and so it
now holds the greenhouse controller language. The Schedule.ghs file
is the one created by the end user to control the greenhouse. Heres an
example:
//:! c09:Schedule.ghs
Bell(7.00)
ThermostatDay(6.00)
WaterOn(3.30)
LightOn(1.00)
ThermostatNight(5.00)
LightOff(2.00)
WaterOff(4.45)
///:~
This is the goal of the interpreter design pattern: to make the
configuration of your program as simple as possible for the end user.
With Jython you can achieve this with almost no effort at all.
Putting data in
To inject data into your Python program, the PythonInterpreter class
has a deceptively simple method: set( ). However, set( ) takes many
different data types and performs conversions upon them. The following
example is a reasonably thorough exercise of the various set( )
possibilities, along with comments that should give a fairly complete
explanation:
//: c09:PythonInterpreterSetting.java
// Passing data from Java to python when using
// the PythonInterpreter object.
import org.python.util.PythonInterpreter;
import org.python.core.*;
import java.util.*;
import com.bruceeckel.python.*;
import com.bruceeckel.test.*;
public class
PythonInterpreterSetting extends UnitTest {
PythonInterpreter interp =
new PythonInterpreter();
public void test() throws PyException {
// It automatically converts Strings
// into native Python strings:
knows what to do with it, but if you want to pass in a primitive you must
perform a conversion. One way to do this is to create a Py type, such as
PyInteger or PyFloat. but it turns out you can also use Javas own
object wrappers like Integer and Float, which is probably going to be a
lot easier to remember.
Early in the program youll see an exec( ) containing the Python
statement:
print a[5:]
The colon inside the indexing statement indicates a Python slice, which
produces a range of elements from the original array. In this case, it
produces an array containing the elements from number 5 until the end
of the array. You could also say a[3:5] to produce elements 3 through 5,
or a[:5] to produce the elements zero through 5. The reason a slice is
used in this statement is to make sure that the Java String has really
been converted to a Python string, which can also be treated as an array of
characters.
You can see that its possible, using exec( ), to create a Python function
(although its a bit awkward). The prt( ) function prints the whole array,
and then (to make sure its a real Python array), iterates through each
element of the array and prints it. Finally, it prints the class of the array,
so we can see what conversion has taken place (Python not only has runtime type information, it also has the equivalent of Java reflection). The
prt( ) function is used to print arrays that come from each of the Java
primitive types.
Although a Java ArrayList does pass into the interpreter using set( ),
and you can index into it as if it were an array, trying to create a slice fails.
To completely convert it into an array, one approach is to simply extract a
Java array using toArray( ), and pass that in. The set( ) method
converts it to a PyArray one of the classes provided with Jython
which can be treated as a Python array (you can also explicitly create a
PyArray, but this seems unnecessary).
Finally, a Map is created and passed directly into the interpreter. While it
is possible to do simple things like index into the resulting object, its not
a real Python dictionary so you cant (for example) call the keys( )
method. There is no straightforward way to convert a Java Map into a
Python dictionary, and so I wrote a utility called toPyDictionary( ) and
made it a static method of com.bruceeckel.python.PyUtil. This also
includes utilities to extract a Python array into a Java List, and a Python
dictionary into a Java Map:
//: com:bruceeckel:python:PyUtil.java
// PythonInterpreter utilities
package com.bruceeckel.python;
import org.python.util.PythonInterpreter;
import org.python.core.*;
import java.util.*;
public class PyUtil {
/** Extract a Python tuple or array into a Java
List (which can be converted into other kinds
of lists and sets inside Java).
@param interp The Python interpreter object
@param pyName The id of the python list object
*/
public static List
toList(PythonInterpreter interp, String pyName){
return new ArrayList(Arrays.asList(
(Object[])interp.get(
pyName, Object[].class)));
}
/** Extract a Python dictionary into a Java Map
@param interp The Python interpreter object
@param pyName The id of the python dictionary
*/
public static Map
toMap(PythonInterpreter interp, String pyName){
PyList pa = ((PyDictionary)interp.get(
pyName)).items();
Map map = new HashMap();
while(pa.__len__() != 0) {
PyTuple po = (PyTuple)pa.pop();
Object first = po.__finditem__(0)
.__tojava__(Object.class);
Object second = po.__finditem__(1)
.__tojava__(Object.class);
map.put(first, second);
}
return map;
}
/** Turn a Java Map into a PyDictionary,
suitable for placing into a PythonInterpreter
@param map The Java Map object
*/
public static PyDictionary
toPyDictionary(Map map) {
Map m = new HashMap();
Iterator it = map.entrySet().iterator();
while(it.hasNext()) {
Map.Entry e = (Map.Entry)it.next();
m.put(Py.java2py(e.getKey()),
Py.java2py(e.getValue()));
}
// PyDictionary constructor wants a Hashtable:
return new PyDictionary(new Hashtable(m));
}
} ///:~
Here is the (black-box) unit testing code:
//: com:bruceeckel:python:Test.java
package com.bruceeckel.python;
import org.python.util.PythonInterpreter;
import java.util.*;
import com.bruceeckel.test.*;
public class Test extends UnitTest {
PythonInterpreter pi =
new PythonInterpreter();
public void test1() {
pi.exec("tup=('fee','fi','fo','fum','fi')");
List lst = PyUtil.toList(pi, "tup");
System.out.println(lst);
System.out.println(new HashSet(lst));
}
public void test2() {
pi.exec("ints=[1,3,5,7,9,11,13,17,19]");
List lst = PyUtil.toList(pi, "ints");
System.out.println(lst);
}
public void test3() {
pi.exec("dict = { 1 : 'a', 3 : 'b', " +
"5 : 'c', 9 : 'd', 11 : 'e'}");
Map mp = PyUtil.toMap(pi, "dict");
System.out.println(mp);
}
public void test4() {
Map m = new HashMap();
m.put("twas", new Integer(11));
m.put("brillig", new Integer(27));
m.put("and", new Integer(47));
2.
import
import
import
import
import
org.python.util.PythonInterpreter;
org.python.core.*;
java.util.*;
com.bruceeckel.python.*;
com.bruceeckel.test.*;
public class
PythonInterpreterGetting extends UnitTest{
PythonInterpreter interp =
new PythonInterpreter();
public void test() throws PyException {
interp.exec("a = 100");
// If you just use the ordinary get(),
// it returns a PyObject:
PyObject a = interp.get("a");
// There's not much you can do with a generic
// PyObject, but you can print it out:
System.out.println("a = " + a);
// If you know the type it's supposed to be,
// you can "cast" it using __tojava__() to
// that Java type and manipulate it in Java.
// To use 'a' as an int, you must use
// the Integer wrapper class:
int ai= ((Integer)a.__tojava__(Integer.class))
.intValue();
// There are also convenience functions:
ai = Py.py2int(a);
System.out.println("ai + 47 = " + (ai + 47));
// You can convert it to different types:
float af = Py.py2float(a);
System.out.println("af + 47 = " + (af + 47));
// If you try to cast it to an inappropriate
// type you'll get a runtime exception:
//! String as = (String)a.__tojava__(
//!
String.class);
// If you know the type, a more useful method
// is the overloaded get() that takes the
// desired class as the 2nd argument:
interp.exec("x = 1 + 2");
int x = ((Integer)interp
.get("x", Integer.class)).intValue();
System.out.println("x = " + x);
Multiple interpreters
Its also worth noting that you can have multiple PythonInterpreter
objects in a program, and each one has its own name space:
//: c09:MultipleJythons.java
// You can run multiple interpreters, each
// with its own name space.
import org.python.util.PythonInterpreter;
import org.python.core.*;
import com.bruceeckel.test.*;
public class MultipleJythons extends UnitTest {
PythonInterpreter
interp1 = new PythonInterpreter(),
interp2 = new PythonInterpreter();
Note that the import statements map to the Java package structure
exactly as you would expect. In the first example, a Date( ) object is
created as if it were a native Python class, and printing this object just
calls toString( ).
ValGen implements the concept of a generator which is used a great
deal in the C++ STL (Standard Template Library, part of the Standard
C++ Library). A generator is an object that produces a new object every
time its generation method is called, and it is quite convenient for filling
containers. Here, I wanted to use it in a for iteration, and so I needed the
generation method to be the one that is called by the iteration process.
This is a special method called __getitem__( ), which is actually the
overloaded operator for indexing, [ ]. A for loop calls this method every
time it wants to move the iteration forward, and when the elements run
out, __getitem__( ) throws an out-of-bounds exception and that
signals the end of the for loop (in other languages, you would never use
an exception for ordinary control flow, but in Python it seems to work
quite well). This exception happens automatically when self.val[i] runs
out of elements, so the __getitem__( ) code turns out to be simple. The
only complexity is that __getitem__( ) appears to return two objects
instead of just one. What Python does is automatically package multiple
return values into a tuple, so you still only end up returning a single
object (in C++ or Java you would have to create your own data structure
to accomplish this). In addition, in the for loop where ValGen is used,
Python automatically unpacks the tuple so that you can have multiple
iterators in the for. These are the kinds of syntax simplifications that
make Python so endearing.
The map and set objects are instances of Javas HashMap and
HashSet, again created as if those classes were just native Python
components. In the for loop, the put( ) and add( ) methods work just
like they do in Java. Also, indexing into a Java Map uses the same
notation as for dictionaries, but note that to iterate through the keys in a
Map you must use the Map method keySet( ) rather than the Python
dictionary method keys( ).
The final part of the example shows the use of a Java class that I created
from scratch, to demonstrate how trivial it is. Notice also that Jython
intuitively understands JavaBeans properties, since you can either use the
getVal( ) and setVal( ) methods, or assign to and read from the
equivalent val property. Also, getChars( ) returns a Character[] in
Java, and this becomes an array in Python.
The easiest way to use Java classes that you create for use inside a Python
program is to put them inside a package. Although Jython can also
import unpackaged java classes (import JavaClass), all such
unpackaged java classes will be treated as if they were defined in different
packages so they can only see each others public methods.
Java packages translate into Python modules, and Python must import a
module in order to be able to use the Java class. Here is the Java code for
JavaClass:
//: c09:javaclass:JavaClass.java
package c09.javaclass;
import com.bruceeckel.test.*;
import com.bruceeckel.util.*;
public class JavaClass {
private String s = "";
public JavaClass() {
System.out.println("JavaClass()");
}
public JavaClass(String a) {
s = a;
System.out.println("JavaClass(String)");
}
public String getVal() {
System.out.println("getVal()");
return s;
}
public void setVal(String a) {
System.out.println("setVal()");
s = a;
}
public Character[] getChars() {
System.out.println("getChars()");
Character[] r = new Character[s.length()];
for(int i = 0; i < s.length(); i++)
r[i] = new Character(s.charAt(i));
return r;
}
public static class Test extends UnitTest {
JavaClass
x1 = new JavaClass(),
x2 = new JavaClass("UnitTest");
public void test1() {
System.out.println(x2.getVal());
x1.setVal("SpamEggsSausageAndSpam");
Arrays2.print(x1.getChars());
}
}
public static void main(String[] args) {
Test test = new Test();
test.test1();
}
} ///:~
You can see that this is just an ordinary Java class, without any awareness
that it will be used in a Jython program. For this reason, one of the
important uses of Jython is in testing Java code2. Because Python is such
a powerful, flexible, dynamic language it is an ideal tool for automated
test frameworks, without making any changes to the Java code thats
being tested.
Inner Classes
Inner classes becomes attributes on the class object. Instances of static
inner classes can be create by the usual call:
com.foo.JavaClass.StaticInnerClass()
Non-static inner classes must have an outer class instance supplied
explicitly as the first argument:
com.foo.JavaClass.InnerClass(com.foo.JavaClass())
#: c09:PythonSwing.py
# The HTMLButton.java example from
# "Thinking in Java, 2nd edition," Chapter 13,
# converted into Jython.
# Dont run this as part of the automatic make:
#=M @echo skipping PythonSwing.py
from javax.swing import JFrame, JButton, JLabel
from java.awt import FlowLayout
frame = JFrame("HTMLButton", visible=1,
defaultCloseOperation=JFrame.EXIT_ON_CLOSE)
def kapow(e):
frame.contentPane.add(JLabel("<html>"+
"<i><font size=+4>Kapow!"))
# Force a re-layout to
# include the new label:
frame.validate()
button = JButton("<html><b><font size=+2>" +
"<center>Hello!<br><i>Press me now!",
actionPerformed=kapow)
frame.contentPane.layout = FlowLayout()
frame.contentPane.add(button)
frame.pack()
frame.size=200, 500
#:~
If you compare the Java version of the program to the above Jython
implementation, youll see that Jython is shorter and generally easier to
understand. For example, in the Java version to set up the frame you had
to make several calls: the constructor for JFrame( ), the setVisible( )
method and the setDefaultCloseOperation( ) method, whereas in the
above code all three of these operations are performed with a single
constructor call.
Also notice that the JButton is configured with an actionListener( )
method inside the constructor, with the assignment to kapow. In
addition, Jythons JavaBean awareness means that a call to any method
with a name that begins with set can be replaced with an assignment,
as you can see above.
The only method that did not come over from Java is the pack( ) method,
which seems to be essential in order to force the layout to happen
properly. Its also important that the call to pack( ) appear before the
size setting.
generates the returned value after the colon. As you should know, the
Java prototype for the actionPerformed( ) method only contains a
single argument, but the lambda expression indicates two. However, the
second argument is provided with a default value, so the function can be
called with only one argument. The reason for the second argument is
seen in the default value, because this is a way to pass self into the
lambda expression, so that it can be used to dispose of the dialog.
Compare this code with the version thats published in Thinking in Java,
2nd edition. Youll find that Python language features allow a much more
succinct and direct implementation.
Theres one other set of rules you must follow in order to get a successful
compilation: you must inherit from a Java class or interface in your
Python class (you do not need to specify the @sig signature for methods
defined in the superclass/interface). If you do not do this, you wont get
your desired methods unfortunately, jythonc gives you no warnings or
errors in this case, but you wont get what you want. If you dont see
whats missing, it can be very frustrating.
In addition, you must import the appropriate java class and give the
correct package specification. In the example below, java is imported so
you must inherit from java.lang.Object, but you could also say from
java.lang import Object and then youd just inherit from Object
without the package specification. Unfortunately, you dont get any
warnings or errors if you get this wrong, so you must be patient and keep
trying.
Here is an example of a Python class created to produce a Java class. This
also introduces the =T directive for the makefile builder tool, which
specifies a different target than the one that is normally used by the tool.
In this case, the Python file is used to build a Java .class file, so the class
file is the desired makefile target. To accomplish this, the default makefile
command is replaced using the =M directive (notice how you can break
across lines using \):
#: c09:PythonToJavaClass.py
#=T python\java\test\PythonToJavaClass.class
#=M jythonc.bat --package python.java.test \
#=M PythonToJavaClass.py
# A Python class created to produce a Java class
from jarray import array
import java
class PythonToJavaClass(java.lang.Object):
# The '@sig' signature string is used to create
# the proper signature in the resulting
# Java code:
def __init__(self):
"@sig public PythonToJavaClass()"
print "Constructor for PythonToJavaClass"
def simple(self):
"@sig public void simple()"
print "simple()"
print
print
print
print
"a: ", a
"element type:", a[0].__class__
"a[3] + a[5]:", a[5] + a[7]
"a[2:5]:", a[2:5] # A real Python array
Here is the Java program to exercise the Java classes produced by the
above Python code. This also introduces the =D directive for the
makefile builder tool, which specifies a dependency in addition to those
detected by the tool. Here, you cant compile
TestPythonToJavaClass.java until PythonToJavaClass.class is
available:
//: c09:TestPythonToJavaClass.java
//+D python\java\test\PythonToJavaClass.class
import java.lang.reflect.*;
import java.util.*;
import org.python.core.*;
import com.bruceeckel.test.*;
import com.bruceeckel.util.*;
import com.bruceeckel.python.*;
// The package with the Python-generated classes:
import python.java.test.*;
public class
TestPythonToJavaClass extends UnitTest {
PythonToJavaClass p2j = new PythonToJavaClass();
public void test1() {
p2j.simple();
System.out.println(p2j.returnString());
Arrays2.print(p2j.returnArray());
Arrays2.print(p2j.ints());
Arrays2.print(p2j.doubles());
p2j.argIn1("Testing argIn1()");
p2j.argIn2(new Integer(47));
ArrayList a = new ArrayList();
for(int i = 0; i < 10; i++)
a.add(new Integer(i));
p2j.argIn3(a);
p2j.argIn4(
new PyArray(Integer.class, a.toArray()));
Map m = new HashMap();
for(int i = 0; i < 10; i++)
m.put("" + i, new Float(i));
p2j.argIn5(PyUtil.toPyDictionary(m));
}
public void dumpClassInfo() {
Arrays2.print(
p2j.getClass().getConstructors());
Method[] methods =
p2j.getClass().getMethods();
for(int i = 0; i < methods.length; i++) {
String nm = methods[i].toString();
if(nm.indexOf("PythonToJavaClass") != -1)
System.out.println(nm);
}
}
public static void main(String[] args) {
TestPythonToJavaClass test =
new TestPythonToJavaClass();
test.dumpClassInfo();
test.test1();
}
} ///:~
For Python support, youll usually only need to import the classes in
org.python.core. Everything else in the above example is fairly
straightforward, as PythonToJavaClass appears, from the Java side, to
be just another Java class. dumpClassInfo( ) uses reflection to verify
that the method signatures specified in PythonToJavaClass.py have
come through properly.
Summary
This chapter has arguably gone much deeper into Jython than required to
use the interpreter design pattern. Indeed, once you decide that you need
to use interpreter and that youre not going to get lost inventing your own
language, the solution of installing Jython is quite simple, and you can at
least get started by following the GreenHouseController example.
Of course, that example is often too simple and you may need something
more sophisticated, often requiring more interesting data to be passed
back and forth. When I encountered the limited documentation, I felt it
necessary to come up with a more thorough examination of Jython.
In the process, note that there could be another equally powerful design
pattern lurking in here, which could perhaps be called multiple languages.
This is based on the experience of having each language solve a certain
class of problems better than the other; by combining languages you can
solve problems much faster than with either language by itself. CORBA is
another way to bridge across languages, and at the same time bridging
between computers and operating systems.
To me, Python and Java present a very potent combination for program
development because of Javas architecture and tool set, and Pythons
extremely rapid development (generally considered to be 5-10 times
faster than C++ or Java). Python is usually slower, however, but even if
you end up re-coding parts of your program for speed, the initial fast
development will allow you to more quickly flesh out the system and
uncover and solve the critical sections. And often, the execution speed of
Python is not a problem in those cases its an even bigger win. A
number of commercial products already use Java and Jython, and
because of the terrific productivity leverage I expect to see this happen
more in the future.
Exercises
1.
2.
3.
4.
5.
10: Callbacks
Decoupling code behavior
Observer, and a category of callbacks called multiple dispatching (not in
Design Patterns) including the Visitor from Design Patterns.
Observer
Like the other forms of callback, this contains a hook point where you can
change code. The difference is in the observers completely dynamic
nature. It is often used for the specific case of changes based on other
objects change of state, but is also the basis of event management.
Anytime you want to decouple the source of the call from the called code
in a completely dynamic way.
The observer pattern solves a fairly common problem: What if a group of
objects needs to update themselves when some object changes state? This
can be seen in the model-view aspect of Smalltalks MVC (model-viewcontroller), or the almost-equivalent Document-View Architecture.
Suppose that you have some data (the document) and more than one
view, say a plot and a textual view. When you change the data, the two
views must know to update themselves, and thats what the observer
facilitates. Its a common enough problem that its solution has been made
a part of the standard java.util library.
There are two types of objects used to implement the observer pattern in
Java. The Observable class keeps track of everybody who wants to be
informed when a change happens, whether the state has changed or not.
When someone says OK, everybody should check and potentially update
themselves, the Observable class performs this task by calling the
notifyObservers( ) method for each one on the list. The
notifyObservers( ) method is part of the base class Observable.
There are actually two things that change in the observer pattern: the
quantity of observing objects and the way an update occurs. That is, the
observer pattern allows you to modify both of these without affecting the
surrounding code.
------------Observer is an interface class that only has one member function,
update( ). This function is called by the object thats being observed,
when that object decides its time to update all its observers. The
arguments are optional; you could have an update( ) with no arguments
and that would still fit the observer pattern; however this is more
generalit allows the observed object to pass the object that caused the
update (since an Observer may be registered with more than one
observed object) and any extra information if thats helpful, rather than
forcing the Observer object to hunt around to see who is updating and
to fetch any other information it needs.
The observed object that decides when and how to do the updating will
be called the Observable.
Observable has a flag to indicate whether its been changed. In a simpler
design, there would be no flag; if something happened, everyone would be
notified. The flag allows you to wait, and only notify the Observers when
you decide the time is right. Notice, however, that the control of the flags
state is protected, so that only an inheritor can decide what constitutes a
change, and not the end user of the resulting derived Observer class.
Most of the work is done in notifyObservers( ). If the changed flag
has not been set, this does nothing. Otherwise, it first clears the changed
flag so repeated calls to notifyObservers( ) wont waste time. This is
done before notifying the observers in case the calls to update( ) do
Observing flowers
Here is an example of the observer pattern:
//: c10:ObservedFlower.java
// Demonstration of "observer" pattern.
import java.util.*;
import com.bruceeckel.test.*;
class Flower {
private boolean isOpen;
private OpenNotifier oNotify =
new OpenNotifier();
private CloseNotifier cNotify =
new CloseNotifier();
public Flower() { isOpen = false; }
public void open() { // Opens its petals
isOpen = true;
oNotify.notifyObservers();
cNotify.open();
}
public void close() { // Closes its petals
isOpen = false;
cNotify.notifyObservers();
oNotify.close();
}
public Observable opening() { return oNotify; }
public Observable closing() { return cNotify; }
private class OpenNotifier extends Observable {
private boolean alreadyOpen = false;
public void notifyObservers() {
if(isOpen && !alreadyOpen) {
setChanged();
super.notifyObservers();
alreadyOpen = true;
}
}
public void close() { alreadyOpen = false; }
}
private class CloseNotifier extends Observable{
private boolean alreadyClosed = false;
public void notifyObservers() {
if(!isOpen && !alreadyClosed) {
setChanged();
super.notifyObservers();
alreadyClosed = true;
}
}
public void open() { alreadyClosed = false; }
}
}
class Bee {
private String name;
private OpenObserver openObsrv =
new OpenObserver();
private CloseObserver closeObsrv =
new CloseObserver();
public Bee(String nm) { name = nm; }
// An inner class for observing openings:
private class OpenObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Bee " + name
+ "'s breakfast time!");
}
}
// Another inner class for closings:
private class CloseObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Bee " + name
+ "'s bed time!");
}
}
public Observer openObserver() {
return openObsrv;
}
f.opening().addObserver(bb.openObserver());
f.closing().addObserver(ha.closeObserver());
f.closing().addObserver(hb.closeObserver());
f.closing().addObserver(ba.closeObserver());
f.closing().addObserver(bb.closeObserver());
// Hummingbird B decides to sleep in:
f.opening().deleteObserver(
hb.openObserver());
// A change that interests observers:
f.open();
f.open(); // It's already open, no change.
// Bee A doesn't want to go to bed:
f.closing().deleteObserver(
ba.closeObserver());
f.close();
f.close(); // It's already closed; no change
f.opening().deleteObservers();
f.open();
f.close();
}
public static void main(String args[]) {
new ObservedFlower().test();
}
} ///:~
The events of interest are that a Flower can open or close. Because of the
use of the inner class idiom, both these events can be separately
observable phenomena. OpenNotifier and CloseNotifier both inherit
Observable, so they have access to setChanged( ) and can be handed
to anything that needs an Observable.
The inner class idiom also comes in handy to define more than one kind
of Observer, in Bee and Hummingbird, since both those classes may
want to independently observe Flower openings and closings. Notice
how the inner class idiom provides something that has most of the
benefits of inheritance (the ability to access the private data in the outer
class, for example) without the same restrictions.
In main( ), you can see one of the prime benefits of the observer pattern:
the ability to change behavior at run time by dynamically registering and
un-registering Observers with Observables.
If you study the code above youll see that OpenNotifier and
CloseNotifier use the basic Observable interface. This means that you
A visual example of
observers
The following example is similar to the ColorBoxes example from
Chapter 14 in Thinking in Java, 2nd Edition. Boxes are placed in a grid on
the screen and each one is initialized to a random color. In addition, each
box implements the Observer interface and is registered with an
Observable object. When you click on a box, all of the other boxes are
notified that a change has been made because the Observable object
automatically calls each Observer objects update( ) method. Inside
this method, the box checks to see if its adjacent to the one that was
clicked, and if so it changes its color to match the clicked box.
//: c10:BoxObserver.java
// Demonstration of Observer pattern using
// Java's built-in observer classes.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;
// You must inherit a new type of Observable:
class BoxObservable extends Observable {
public void notifyObservers(Object b) {
// Otherwise it won't propagate changes:
setChanged();
super.notifyObservers(b);
}
}
public class BoxObserver extends JFrame {
Observable notifier = new BoxObservable();
public BoxObserver(int grid) {
setTitle("Demonstrates Observer pattern");
Container cp = getContentPane();
cp.setLayout(new GridLayout(grid, grid));
for(int x = 0; x < grid; x++)
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
notifier.notifyObservers(OCBox.this);
}
}
public void update(Observable o, Object arg) {
OCBox clicked = (OCBox)arg;
if(nextTo(clicked)) {
cColor = clicked.cColor;
repaint();
}
}
private final boolean nextTo(OCBox b) {
return Math.abs(x - b.x) <= 1 &&
Math.abs(y - b.y) <= 1;
}
} ///:~
When you first look at the online documentation for Observable, its a
bit confusing because it appears that you can use an ordinary
Observable object to manage the updates. But this doesnt work; try it
inside BoxObserver, create an Observable object instead of a
BoxObservable object and see what happens: nothing. To get an effect,
you must inherit from Observable and somewhere in your derived-class
code call setChanged( ). This is the method that sets the changed flag,
which means that when you call notifyObservers( ) all of the observers
will, in fact, get notified. In the example above setChanged( ) is simply
called within notifyObservers( ), but you could use any criterion you
want to decide when to call setChanged( ).
BoxObserver contains a single Observable object called notifier, and
every time an OCBox object is created, it is tied to notifier. In OCBox,
whenever you click the mouse the notifyObservers( ) method is called,
passing the clicked object in as an argument so that all the boxes receiving
the message (in their update( ) method) know who was clicked and can
decide whether to change themselves or not. Using a combination of code
in notifyObservers( ) and update( ) you can work out some fairly
complex schemes.
It might appear that the way the observers are notified must be frozen at
compile time in the notifyObservers( ) method. However, if you look
more closely at the code above youll see that the only place in
Exercises
1.
2.
11: Multiple
dispatching
When dealing with multiple types which are interacting, a program can
get particularly messy. For example, consider a system that parses and
executes mathematical expressions. You want to be able to say Number
+ Number, Number * Number, etc., where Number is the base class
for a family of numerical objects. But when you say a + b, and you dont
know the exact type of either a or b, so how can you get them to interact
properly?
The answer starts with something you probably dont think about: Java
performs only single dispatching. That is, if you are performing an
operation on more than one object whose type is unknown, Java can
invoke the dynamic binding mechanism on only one of those types. This
doesnt solve the problem, so you end up detecting some types manually
and effectively producing your own dynamic binding behavior.
The solution is called multiple dispatching. Remember that
polymorphism can occur only via member function calls, so if you want
double dispatching to occur, there must be two member function calls:
the first to determine the first unknown type, and the second to
determine the second unknown type. With multiple dispatching, you
must have a polymorphic method call to determine each of the types.
Generally, youll set up a configuration such that a single member
function call produces more than one dynamic member function call and
thus determines more than one type in the process. To get this effect, you
need to work with more than one polymorphic method call: youll need
one call for each dispatch. The methods in the following example are
called compete( ) and eval( ), and are both members of the same type.
(In this case there will be only two dispatches, which is referred to as
double dispatching). If you are working with two different type
hierarchies that are interacting, then youll have to have a polymorphic
method call in each hierarchy.
Heres an example of multiple dispatching:
//: c11:PaperScissorsRock.java
// Demonstration of multiple dispatching.
import java.util.*;
import com.bruceeckel.test.*;
// An enumeration type:
class Outcome {
private int value;
private String name;
private Outcome(int val, String nm) {
value = val;
name = nm;
}
public final static Outcome
WIN = new Outcome(0, "win"),
LOSE = new Outcome(1, "lose"),
DRAW = new Outcome(2, "draw");
public String toString() { return name; }
public boolean equals(Object o) {
return (o instanceof Outcome)
Item {
compete(Item it);
eval(Paper p);
eval(Scissors s);
eval(Rock r);
items2.add(ItemGenerator.newItem());
}
}
public void test() {
for(int i = 0; i < SIZE; i++)
System.out.println(
Compete.match(
(Item)items1.get(i),
(Item)items2.get(i)));
}
public static void main(String args[]) {
new PaperScissorsRock().test();
}
} ///:~
}
public void visit(Chrysanthemum c) {
System.out.println("Bee and Chrysanthemum");
}
}
class FlowerGenerator {
public static Flower newFlower() {
switch((int)(Math.random() * 3)) {
default:
case 0: return new Gladiolus();
case 1: return new Renuculus();
case 2: return new Chrysanthemum();
}
}
}
public class BeeAndFlowers extends UnitTest {
List flowers = new ArrayList();
public BeeAndFlowers() {
for(int i = 0; i < 10; i++)
flowers.add(FlowerGenerator.newFlower());
}
public void test() {
// It's almost as if I had a function to
// produce a Flower string representation:
StringVal sval = new StringVal();
Iterator it = flowers.iterator();
while(it.hasNext()) {
((Flower)it.next()).accept(sval);
System.out.println(sval);
}
// Perform "Bee" operation on all Flowers:
Bee bee = new Bee();
it = flowers.iterator();
while(it.hasNext())
((Flower)it.next()).accept(bee);
}
public static void main(String args[]) {
new BeeAndFlowers().test();
}
} ///:~
Exercises
1.
2.
3.
4.
12: Pattern
refactoring
This chapter will look at the process of solving a problem by applying
design patterns in an evolutionary fashion. That is, a first cut design will
be used for the initial solution, and then this solution will be examined
and various design patterns will be applied to the problem (some of which
will work, and some of which wont). The key question that will always be
asked in seeking improved solutions is what will change?
This process is similar to what Martin Fowler talks about in his book
Refactoring: Improving the Design of Existing Code1 (although he tends
to talk about pieces of code more than pattern-level designs). You start
with a solution, and then when you discover that it doesnt continue to
meet your needs, you fix it. Of course, this is a natural tendency but in
computer programming its been extremely difficult to accomplish with
procedural programs, and the acceptance of the idea that we can refactor
code and designs adds to the body of proof that object-oriented
programming is a good thing.
Addison-Wesley, 1999.
encounter in your work. The extra constraint is that the trash arrives at
the trash recycling plant all mixed together. The program must model the
sorting of that trash. This is where RTTI comes in: you have a bunch of
anonymous pieces of trash, and the program figures out exactly what type
they are.
//: c12:recyclea:RecycleA.java
// Recycling with RTTI.
import java.util.*;
import java.io.*;
import com.bruceeckel.test.*;
abstract class Trash {
private double weight;
Trash(double wt) { weight = wt; }
abstract double getValue();
double getWeight() { return weight; }
// Sums the value of Trash in a bin:
static void sumValue(Iterator it) {
double val = 0.0f;
while(it.hasNext()) {
// One kind of RTTI:
// A dynamically-checked cast
Trash t = (Trash)it.next();
// Polymorphism in action:
val += t.getWeight() * t.getValue();
System.out.println(
"weight of " +
// Using RTTI to get type
// information about the class:
t.getClass().getName() +
" = " + t.getWeight());
}
System.out.println("Total value = " + val);
}
}
class Aluminum extends Trash {
static double val = 1.67f;
Aluminum(double wt) { super(wt); }
double getValue() { return val; }
static void setValue(double newval) {
val = newval;
}
}
class Paper extends Trash {
static double val = 0.10f;
Paper(double wt) { super(wt); }
double getValue() { return val; }
static void setValue(double newval) {
val = newval;
}
}
class Glass extends Trash {
static double val = 0.23f;
Glass(double wt) { super(wt); }
double getValue() { return val; }
static void setValue(double newval) {
val = newval;
}
}
public class RecycleA extends UnitTest {
Collection
bin = new ArrayList(),
glassBin = new ArrayList(),
paperBin = new ArrayList(),
alBin = new ArrayList();
public RecycleA() {
// Fill up the Trash bin:
for(int i = 0; i < 30; i++)
switch((int)(Math.random() * 3)) {
case 0 :
bin.add(new
Aluminum(Math.random() * 100));
break;
case 1 :
bin.add(new
Paper(Math.random() * 100));
break;
case 2 :
bin.add(new
Glass(Math.random() * 100));
}
}
public void test() {
It looks silly to upcast the types of Trash into a container holding base
type references, and then turn around and downcast. Why not just put the
trash into the appropriate receptacle in the first place? (Indeed, this is the
whole enigma of recycling). In this program it would be easy to repair, but
sometimes a systems structure and flexibility can benefit greatly from
downcasting.
The program satisfies the design requirements: it works. This might be
fine as long as its a one-shot solution. However, a useful program tends
to evolve over time, so you must ask, What if the situation changes? For
example, cardboard is now a valuable recyclable commodity, so how will
that be integrated into the system (especially if the program is large and
complicated). Since the above type-check coding in the switch statement
could be scattered throughout the program, you must go find all that code
every time a new type is added, and if you miss one the compiler wont
give you any help by pointing out an error.
The key to the misuse of RTTI here is that every type is tested. If youre
looking for only a subset of types because that subset needs special
treatment, thats probably fine. But if youre hunting for every type inside
a switch statement, then youre probably missing an important point, and
definitely making your code less maintainable. In the next section well
look at how this program evolved over several stages to become much
more flexible. This should prove a valuable example in program design.
The idea of the factory method is that you pass it the essential
information it needs to know to create your object, then stand back and
wait for the reference (already upcast to the base type) to pop out as the
return value. From then on, you treat the object polymorphically. Thus,
you never even need to know the exact type of object thats created. In fact,
the factory method hides it from you to prevent accidental misuse. If you
want to use the object without polymorphism, you must explicitly use
RTTI and casting.
But theres a little problem, especially when you use the more complicated
approach (not shown here) of making the factory method in the base class
and overriding it in the derived classes. What if the information required
in the derived class requires more or different arguments? Creating more
objects solves this problem. To implement the factory method, the
Trash class gets a new method called factory. To hide the creational
data, theres a new class called Messenger that carries all of the
necessary information for the factory method to create the appropriate
Trash object (weve started referring to Messenger as a design pattern,
but its simple enough that you may not choose to elevate it to that status).
Heres a simple implementation of Messenger:
class Messenger {
int type;
// Must change this to add another type:
static final int MAX_NUM = 4;
double data;
Messenger(int typeNum, double val) {
type = typeNum % MAX_NUM;
data = val;
}
}
A Messenger objects only job is to hold information for the factory( )
method. Now, if theres a situation in which factory( ) needs more or
different information to create a new type of Trash object, the factory( )
interface doesnt need to be changed. The Messenger class can be
changed by adding new data and new constructors, or in the more typical
object-oriented fashion of subclassing.
The factory( ) method for this simple example looks like this:
static Trash factory(Messenger i) {
switch(i.type) {
default: // To quiet the compiler
case 0:
return new
case 1:
return new
case 2:
return new
// Two lines
case 3:
return new
Aluminum(i.data);
Paper(i.data);
Glass(i.data);
here:
Cardboard(i.data);
}
}
Here, the determination of the exact type of object is simple, but you can
imagine a more complicated system in which factory( ) uses an
elaborate algorithm. The point is that its now hidden away in one place,
and you know to come to this place when you add new types.
The creation of new objects is now much simpler in main( ):
for(int i = 0; i < 30; i++)
bin.add(
Trash.factory(
new Messenger(
(int)(Math.random() * Messenger.MAX_NUM),
Math.random() * 100)));
A Messenger object is created to pass the data into factory( ), which in
turn produces some kind of Trash object on the heap and returns the
reference thats added to the ArrayList bin. Of course, if you change the
quantity and type of argument, this statement will still need to be
modified, but that can be eliminated if the creation of the Messenger
object is automated. For example, an ArrayList of arguments can be
passed into the constructor of a Messenger object (or directly into a
factory( ) call, for that matter). This requires that the arguments be
parsed and checked at run time, but it does provide the greatest flexibility.
You can see from this code what vector of change problem the factory is
responsible for solving: if you add new types to the system (the change),
the only code that must be modified is within the factory, so the factory
isolates the effect of that change.
Class object wasnt in the list, and it will attempt to load it. By loading
the prototypes dynamically like this, the Trash class doesnt need to
know what types it is working with, so it doesnt need any modifications
when you add new types. This allows it to be easily reused throughout the
rest of the chapter.
//: c12:trash:Trash.java
// Base class for Trash recycling examples.
package c12.trash;
import java.util.*;
import java.lang.reflect.*;
public abstract class Trash {
private double weight;
public Trash(double wt) { weight = wt; }
public Trash() {}
public abstract double getValue();
public double getWeight() { return weight; }
// Sums the value of Trash given an
// Iterator to any container of Trash:
public static void sumValue(Iterator it) {
double val = 0.0f;
while(it.hasNext()) {
// One kind of RTTI:
// A dynamically-checked cast
Trash t = (Trash)it.next();
val += t.getWeight() * t.getValue();
System.out.println(
"weight of " +
// Using RTTI to get type
// information about the class:
t.getClass().getName() +
" = " + t.getWeight());
}
System.out.println("Total value = " + val);
}
// Remainder of class provides
// support for prototyping:
private static List trashTypes =
new ArrayList();
public static Trash factory(Messenger info) {
for(int i = 0; i < trashTypes.size(); i++) {
// Somehow determine the new type
// to create, and create one:
Class tc = (Class)trashTypes.get(i);
if (tc.getName().indexOf(info.id) != -1) {
try {
// Get the dynamic constructor method
// that takes a double argument:
Constructor ctor = tc.getConstructor(
new Class[]{ double.class });
// Call the constructor
// to create a new object:
return (Trash)ctor.newInstance(
new Object[]{new Double(info.data)});
} catch(Exception ex) {
ex.printStackTrace(System.err);
throw new RuntimeException(
"Cannot Create Trash");
}
}
}
// Class was not in the list. Try to load it,
// but it must be in your class path!
try {
System.out.println("Loading " + info.id);
trashTypes.add(Class.forName(info.id));
} catch(Exception e) {
e.printStackTrace(System.err);
throw new RuntimeException(
"Prototype not found");
}
// Loaded successfully.
// Recursive call should work:
return factory(info);
}
public static class Messenger {
public String id;
public double data;
public Messenger(String name, double val) {
id = name;
data = val;
}
}
} ///:~
The basic Trash class and sumValue( ) remain as before. The rest of
the class supports the prototyping pattern. You first see two inner classes
(which are made static, so they are inner classes only for code
Trash subclasses
To fit into the prototyping scheme, the only thing thats required of each
new subclass of Trash is that it contain a constructor that takes a
double argument. Java reflection handles everything else.
Here are the different types of Trash, each in their own file but part of
the Trash package (again, to facilitate reuse within the chapter):
//: c12:trash:Aluminum.java
// The Aluminum class with prototyping.
package c12.trash;
public class Aluminum extends Trash {
private static double val = 1.67f;
public Aluminum(double wt) { super(wt); }
public double getValue() { return val; }
public static void setValue(double newVal) {
val = newVal;
}
} ///:~
//: c12:trash:Paper.java
// The Paper class with prototyping.
package c12.trash;
public class Paper extends Trash {
private static double val = 0.10f;
public Paper(double wt) { super(wt); }
public double getValue() { return val; }
public static void setValue(double newVal) {
val = newVal;
}
} ///:~
//: c12:trash:Glass.java
// The Glass class with prototyping.
package c12.trash;
public class Glass extends Trash {
private static double val = 0.23f;
public Glass(double wt) { super(wt); }
public double getValue() { return val; }
public static void setValue(double newVal) {
val = newVal;
}
} ///:~
And heres a new type of Trash:
//: c12:trash:Cardboard.java
// The Cardboard class with prototyping.
package c12.trash;
public class Cardboard extends Trash {
private static double val = 0.23f;
public Cardboard(double wt) { super(wt); }
public double getValue() { return val; }
public static void setValue(double newVal) {
val = newVal;
}
} ///:~
You can see that, other than the constructor, theres nothing special about
any of these classes.
c12.trash.Aluminum:76
c12.trash.Cardboard:96
c12.trash.Aluminum:25
c12.trash.Aluminum:34
c12.trash.Glass:11
c12.trash.Glass:68
c12.trash.Glass:43
c12.trash.Aluminum:27
c12.trash.Cardboard:44
c12.trash.Aluminum:18
c12.trash.Paper:91
c12.trash.Glass:63
c12.trash.Glass:50
c12.trash.Glass:80
c12.trash.Aluminum:81
c12.trash.Cardboard:12
c12.trash.Glass:12
c12.trash.Glass:54
c12.trash.Aluminum:36
c12.trash.Aluminum:93
c12.trash.Glass:93
c12.trash.Paper:80
c12.trash.Glass:36
c12.trash.Glass:12
c12.trash.Glass:60
c12.trash.Paper:66
c12.trash.Aluminum:36
c12.trash.Cardboard:22
///:~
Note that the class path must be included when giving the class names,
otherwise the class will not be found.
This file is read using the previously-defined StringList tool, and each
line is picked aparat using the String method indexOf( ) to produce the
index of the :. This is first used with the String method substring( ) to
extract the name of the trash type, and next to get the weight that is
turned into a double with the static Double.valueOf( ) method. The
trim( ) method removes white space at both ends of a string.
The Trash parser is placed in a separate file since it will be reused
throughout this chapter:
//: c12:trash:ParseTrash.java
// Parse file contents into Trash objects,
// placing each into a Fillable holder.
package c12.trash;
import java.util.*;
import java.io.*;
import com.bruceeckel.util.StringList;
public class ParseTrash {
public static void
fillBin(String filename, Fillable bin) {
Iterator it =
new StringList(filename).iterator();
while(it.hasNext()) {
String line = (String)it.next();
String type = line.substring(0,
line.indexOf(':')).trim();
double weight = Double.valueOf(
line.substring(line.indexOf(':') + 1)
.trim()).doubleValue();
bin.addTrash(
Trash.factory(
new Trash.Messenger(type, weight)));
}
}
// Special case to handle Collection:
public static void
fillBin(String filename, Collection bin) {
fillBin(filename, new FillableCollection(bin));
}
} ///:~
In RecycleA.java, an ArrayList was used to hold the Trash objects.
However, other types of containers can be used as well. To allow for this,
the first version of fillBin( ) takes a reference to a Fillable, which is
simply an interface that supports a method called addTrash( ):
//: c12:trash:Fillable.java
// Any object that can be filled with Trash.
package c12.trash;
public interface Fillable {
void addTrash(Trash t);
} ///:~
Anything that supports this interface can be used with fillBin. Of course,
Collection doesnt implement Fillable, so it wont work. Since
Collection is used in most of the examples, it makes sense to add a
The program seems to run fine, and yet it never detects any cardboard,
even though there is cardboard in the list! This happens because of the
use of RTTI, which looks for only the types that you tell it to look for. The
clue that RTTI is being misused is that every type in the system is being
tested, rather than a single type or subset of types. As you will see later,
there are ways to use polymorphism instead when youre testing for every
type. But if you use RTTI a lot in this fashion, and you add a new type to
your system, you can easily forget to make the necessary changes in your
program and produce a difficult-to-find bug. So its worth trying to
eliminate RTTI in this case, not just for aesthetic reasonsit produces
more maintainable code.
Abstracting usage
With creation out of the way, its time to tackle the remainder of the
design: where the classes are used. Since its the act of sorting into bins
thats particularly ugly and exposed, why not take that process and hide it
inside a class? This is the principle of If you must do something ugly, at
least localize the ugliness inside a class. It looks like this:
TrashSorter
ArrayList of
Trash Bins
Aluminum ArrayList
Paper ArrayList
Glass ArrayList
do is call a generic method that takes care of the details of type. This, of
course, is another way to describe a dynamically-bound method. So
sort( ) will simply move through the sequence and call a dynamicallybound method for each ArrayList. Since the job of this method is to
grab the pieces of trash it is interested in, its called grab(Trash). The
structure now looks like:
Aluminum ArrayList
TrashSorter
ArrayList of
Trash Bins
boolean grab(Trash)
Paper ArrayList
boolean grab(Trash)
Glass ArrayList
boolean grab(Trash)
Iterator it = bin.iterator();
while(it.hasNext())
trashBins.sort((Trash)it.next());
Iterator e = trashBins.iterator();
while(e.hasNext()) {
Tbin b = (Tbin)e.next();
Trash.sumValue(b.iterator());
}
Trash.sumValue(bin.iterator());
}
public static void main(String args[]) {
new RecycleB().test();
}
} ///:~
Tbin contains a Class reference type which establishes in the
constructor what what type it should grab. The grab() method checks
this type against the object you pass it. Note that in this design, grab()
only accepts Trash objects so you get compile-time type checking on the
base type, but you could also just accept Object and it would still work.
TbinList holds a set of Tbin references, so that sort( ) can iterate
through the Tbins when its looking for a match for the Trash object
youve handed it. If it doesnt find a match, it creates a new Tbin for the
type that hasnt been found, and makes a recursive call to itself the next
time around, the new bin will be found.
Notice the genericity of this code: it doesnt change at all if new types are
added. If the bulk of your code doesnt need changing when a new type is
added (or some other change occurs) then you have an easily extensible
system.
Multiple dispatching
The above design is certainly satisfactory. Adding new types to the system
consists of adding or modifying distinct classes without causing code
changes to be propagated throughout the system. In addition, RTTI is not
misused as it was in RecycleA.java. However, its possible to go one
step further and take a purist viewpoint about RTTI and say that it should
be eliminated altogether from the operation of sorting the trash into bins.
To accomplish this, you must first take the perspective that all typedependent activitiessuch as detecting the type of a piece of trash and
dispatch.
Trash
addToBin(TypedBin[])
Aluminum
Paper
Glass
Cardboard
addToBin(TypedBin[])
addToBin(TypedBin[])
addToBin(TypedBin[])
addToBin(TypedBin[])
TypedBin
add(Aluminum)
add(Paper)
add(Glass)
add(Cardboard)
AluminumBin
add(Aluminum)
PaperBin
add(Paper)
GlassBin
add(Glass)
CardboardBin
add(Cardboard)
The new hierarchy is TypedBin, and it contains its own method called
add( ) that is also used polymorphically. But here's an additional twist:
add( ) is overloaded to take arguments of the different types of trash. So
an essential part of the double dispatching scheme also involves
overloading.Redesigning the program produces a dilemma: its now
necessary for the base class Trash to contain an addToBin( ) method.
One approach is to copy all of the code and change the base class. Another
approach, which you can take when you dont have control of the source
code, is to put the addToBin( ) method into an interface, leave Trash
alone, and inherit new specific types of Aluminum, Paper, Glass, and
Cardboard. This is the approach that will be taken here.
Most of the classes in this design must be public, so they are placed in
their own files. Heres the interface:
//: c12:doubledispatch:TypedBinMember.java
// An interface for adding the double
// dispatching method to the trash hierarchy
// without modifying the original hierarchy.
interface TypedBinMember {
// The new method:
boolean addToBin(TypedBin[] tb);
} ///:~
In each particular subtype of Aluminum, Paper, Glass, and
Cardboard, the addToBin( ) method in the interface
TypedBinMember is implemented, but it looks like the code is exactly
the same in each case:
//: c12:doubledispatch:DDAluminum.java
// Aluminum for double dispatching.
import c12.trash.*;
public class DDAluminum extends Aluminum
implements TypedBinMember {
public DDAluminum(double wt) { super(wt); }
public boolean addToBin(TypedBin[] tb) {
for(int i = 0; i < tb.length; i++)
if(tb[i].add(this))
return true;
return false;
}
} ///:~
//: c12:doubledispatch:DDPaper.java
// Paper for double dispatching.
import c12.trash.*;
public class DDPaper extends Paper
implements TypedBinMember {
public DDPaper(double wt) { super(wt); }
public boolean addToBin(TypedBin[] tb) {
for(int i = 0; i < tb.length; i++)
if(tb[i].add(this))
return true;
return false;
}
} ///:~
//: c12:doubledispatch:DDGlass.java
// Glass for double dispatching.
import c12.trash.*;
public class DDGlass extends Glass
implements TypedBinMember {
public DDGlass(double wt) { super(wt); }
public boolean addToBin(TypedBin[] tb) {
for(int i = 0; i < tb.length; i++)
if(tb[i].add(this))
return true;
return false;
}
} ///:~
//: c12:doubledispatch:DDCardboard.java
// Cardboard for double dispatching.
import c12.trash.*;
public class DDCardboard extends Cardboard
implements TypedBinMember {
public DDCardboard(double wt) { super(wt); }
public boolean addToBin(TypedBin[] tb) {
for(int i = 0; i < tb.length; i++)
if(tb[i].add(this))
return true;
return false;
}
} ///:~
The code in each addToBin( ) calls add( ) for each TypedBin object in
the array. But notice the argument: this. The type of this is different for
each subclass of Trash, so the code is different. (Although this code will
benefit if a parameterized type mechanism is ever added to Java.) So this
is the first part of the double dispatch, because once youre inside this
method you know youre Aluminum, or Paper, etc. During the call to
add( ), this information is passed via the type of this. The compiler
resolves the call to the proper overloaded version of add( ). But since
tb[i] produces a reference to the base type TypedBin, this call will end
up calling a different method depending on the type of TypedBin thats
currently selected. That is the second dispatch.
Heres the base class for TypedBin:
//: c12:doubledispatch:TypedBin.java
// A container for the second dispatch.
import c12.trash.*;
import java.util.*;
public abstract class TypedBin {
Collection c = new ArrayList();
protected boolean addIt(Trash t) {
c.add(t);
return true;
}
public Iterator iterator() {
return c.iterator();
}
public boolean add(DDAluminum a) {
return false;
}
public boolean add(DDPaper a) {
return false;
}
public boolean add(DDGlass a) {
return false;
}
public boolean add(DDCardboard a) {
return false;
}
} ///:~
You can see that the overloaded add( ) methods all return false. If the
method is not overloaded in a derived class, it will continue to return
false, and the caller (addToBin( ), in this case) will assume that the
current Trash object has not been added successfully to a container, and
continue searching for the right container.
In each of the subclasses of TypedBin, only one overloaded method is
overridden, according to the type of bin thats being created. For example,
CardboardBin overrides add(DDCardboard). The overridden
method adds the trash object to its container and returns true, while all
the rest of the add( ) methods in CardboardBin continue to return
false, since they havent been overridden. This is another case in which a
parameterized type mechanism in Java would allow automatic generation
of code. (With C++ templates, you wouldnt have to explicitly write the
subclasses or place the addToBin( ) method in Trash.)
Since for this example the trash types have been customized and placed in
a different directory, youll need a different trash data file to make it work.
Heres a possible DDTrash.dat:
//:! c12:doubledispatch:DDTrash.dat
DDGlass:54
DDPaper:22
DDPaper:11
DDGlass:17
DDAluminum:89
DDPaper:88
DDAluminum:76
DDCardboard:96
DDAluminum:25
DDAluminum:34
DDGlass:11
DDGlass:68
DDGlass:43
DDAluminum:27
DDCardboard:44
DDAluminum:18
DDPaper:91
DDGlass:63
DDGlass:50
DDGlass:80
DDAluminum:81
DDCardboard:12
DDGlass:12
DDGlass:54
DDAluminum:36
DDAluminum:93
DDGlass:93
DDPaper:80
DDGlass:36
DDGlass:12
DDGlass:60
DDPaper:66
DDAluminum:36
DDCardboard:22
///:~
Heres the rest of the program:
//: c12:doubledispatch:DoubleDispatch.java
// Using multiple dispatching to handle more
// than one unknown type during a method call.
import c12.trash.*;
import java.util.*;
import com.bruceeckel.test.*;
class AluminumBin extends TypedBin {
public boolean add(DDAluminum a) {
return addIt(a);
}
}
class PaperBin extends TypedBin {
public boolean add(DDPaper a) {
return addIt(a);
}
}
class GlassBin extends TypedBin {
public boolean add(DDGlass a) {
return addIt(a);
}
}
class CardboardBin extends TypedBin {
public boolean add(DDCardboard a) {
return addIt(a);
}
}
class TrashBinSet {
private TypedBin[] binSet = {
new AluminumBin(),
new PaperBin(),
new GlassBin(),
new CardboardBin()
};
public void sortIntoBins(Collection bin) {
Iterator e = bin.iterator();
while(e.hasNext()) {
TypedBinMember t =
(TypedBinMember)e.next();
if(!t.addToBin(binSet))
System.err.println("Couldn't add " + t);
}
}
public TypedBin[] binSet() { return binSet; }
}
public class DoubleDispatch extends UnitTest {
Collection bin = new ArrayList();
TrashBinSet bins = new TrashBinSet();
public DoubleDispatch() {
// ParseTrash still works, without changes:
ParseTrash.fillBin("DDTrash.dat", bin);
}
public void test() {
// Sort from the master bin into
// the individually-typed bins:
bins.sortIntoBins(bin);
TypedBin[] tb = bins.binSet();
// Perform sumValue for each bin...
for(int i = 0; i < tb.length; i++)
Trash.sumValue(tb[i].c.iterator());
// ... and for the master bin
Trash.sumValue(bin.iterator());
}
public static void main(String args[]) {
new DoubleDispatch().test();
}
} ///:~
TrashBinSet encapsulates all of the different types of TypedBins,
along with the sortIntoBins( ) method, which is where all the double
dispatching takes place. You can see that once the structure is set up,
sorting into the various TypedBins is remarkably easy. In addition, the
efficiency of two dynamic method calls is probably better than any other
way you could sort.
Notice the ease of use of this system in main( ), as well as the complete
independence of any specific type information within main( ). All other
methods that talk only to the Trash base-class interface will be equally
invulnerable to changes in Trash types.
The changes necessary to add a new type are relatively isolated: you
modify TypedBin, inherit the new type of Trash with its addToBin( )
method, then inherit a new TypedBin (this is really just a copy and
simple edit), and finally add a new type into the aggregate initialization
for TrashBinSet.
that you need to add methods to the base class, but you cant touch the
base class. How do you get around this?
The design pattern that solves this kind of problem is called a visitor
(the final one in the Design Patterns book), and it builds on the double
dispatching scheme shown in the last section.
The visitor pattern allows you to extend the interface of the primary type
by creating a separate class hierarchy of type Visitor to virtualize the
operations performed upon the primary type. The objects of the primary
type simply accept the visitor, then call the visitors dynamically-bound
method. It looks like this:
Trash
accept(Visitor)
Aluminum
Paper
Glass
accept(Visitor v) {
v.visit(this);
}
accept(Visitor v) {
v.visit(this);
}
accept(Visitor v) {
v.visit(this);
}
Visitor
visit(Aluminum)
visit(Paper)
visit(Glass)
PriceVisitor
WeightVisitor
visit(Aluminum) {
// Perform Aluminum// specific work
}
visit(Paper) {
// Perform Paper// specific work
}
visit(Glass) {
// Perform Glass// specific work
}
visit(Aluminum) {
// Perform Aluminum// specific work
}
visit(Paper) {
// Perform Paper// specific work
}
visit(Glass) {
// Perform Glass// specific work
}
Etc.
A Reflective Decorator
At this point, you could follow the same approach that was used for
double dispatching and create new subtypes of Aluminum, Paper,
Glass, and Cardboard that implement the accept( ) method. For
example, the new Visitable Aluminum would look like this:
//: c12:trashvisitor:VAluminum.java
// Taking the previous approach of creating a
// specialized Aluminum for the visitor pattern.
import c12.trash.*;
public class VAluminum extends Aluminum
implements Visitable {
public VAluminum(double wt) { super(wt); }
public void accept(Visitor v) {
v.visit(this);
}
} ///:~
However, we seem to be encountering an explosion of interfaces: basic
Trash, special versions for double dispatching, and now more special
versions for visitor. Of course, this explosion of interfaces is arbitrary
one could simply put the additional methods in the Trash class. If we
ignore that we can instead see an opportunity to use the Decorator
pattern: it seems like it should be possible to create a Decorator that can
be wrapped around an ordinary Trash object and will produce the same
interface as Trash and add the extra accept( ) method. In fact, its a
perfect example of the value of Decorator.
The double dispatch creates a problem, however. Since it relies on
overloading of both accept( ) and visit( ), it would seem to require
specialized code for each different version of the accept( ) method. With
C++ templates, this would be fairly easy to accomplish (since templates
automatically generate type-specialized code) but Java has no such
mechanismat least it does not appear to. However, reflection allows you
to determine type information at run time, and it turns out to solve many
problems that would seem to require templates (albeit not as simply).
Heres the decorator that does the trick2:
//: c12:trashvisitor:VisitableDecorator.java
// A decorator that adapts the generic Trash
// classes to the visitor pattern.
import c12.trash.*;
import java.lang.reflect.*;
public class VisitableDecorator
extends Trash implements Visitable {
private Trash delegate;
private Method dispatch;
public VisitableDecorator(Trash t) {
delegate = t;
try {
dispatch = Visitor.class.getMethod (
"visit", new Class[] { t.getClass() }
);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public double getValue() {
return delegate.getValue();
}
public double getWeight() {
return delegate.getWeight();
}
public void accept(Visitor v) {
try {
dispatch.invoke(v, new Object[]{delegate});
} catch (Exception ex) {
ex.printStackTrace();
}
}
} ///:~
[[ Description of Reflection use ]]
This was a solution created by Jaroslav Tulach in a design patterns class that I
gave in Prague.
2
The only other tool we need is a new type of Fillable adapter that
automatically decorates the objects as they are being created from the
original Trash.dat file. But this might as well be a decorator itself,
decorating any kind of Fillable:
//: c12:trashvisitor:FillableVisitor.java
// Adapter Decorator that adds the visitable
// decorator as the Trash objects are
// being created.
import c12.trash.*;
import java.util.*;
public class FillableVisitor
implements Fillable {
private Fillable f;
public FillableVisitor(Fillable ff) { f = ff; }
public void addTrash(Trash t) {
f.addTrash(new VisitableDecorator(t));
}
} ///:~
Now you can wrap it around any kind of existing Fillable, or any new
ones that havent yet been created.
The rest of the program creates specific Visitor types and sends them
through a single list of Trash objects:
//: c12:trashvisitor:TrashVisitor.java
// The "visitor" pattern with VisitableDecorators.
import c12.trash.*;
import java.util.*;
import com.bruceeckel.test.*;
// Specific group of algorithms packaged
// in each implementation of Visitor:
class PriceVisitor implements Visitor {
private double alSum; // Aluminum
private double pSum; // Paper
private double gSum; // Glass
private double cSum; // Cardboard
public void visit(Aluminum al) {
double v = al.getWeight() * al.getValue();
System.out.println(
"value of Aluminum= " + v);
alSum += v;
}
} ///:~
In Test( ), note how visitability is added by simply creating a different
kind of bin using the decorator. Also notice that the FillableCollection
adapter has the appearance of being used as a decorator (for ArrayList)
in this situation. However, it completely changes the interface of the
ArrayList, whereas the definition of Decorator is that the interface of
the decorated class must still be there after decoration.
Note that the shape of the client code (shown in the Test class) has
changed again, from the original approaches to the problem. Now theres
only a single Trash bin. The two Visitor objects are accepted into every
element in the sequence, and they perform their operations. The visitors
keep their own internal data to tally the total weights and prices.
Finally, theres no run time type identification other than the inevitable
cast to Trash when pulling things out of the sequence. This, too, could be
eliminated with the implementation of parameterized types in Java.
One way you can distinguish this solution from the double dispatching
solution described previously is to note that, in the double dispatching
solution, only one of the overloaded methods, add( ), was overridden
when each subclass was created, while here each one of the overloaded
visit( ) methods is overridden in every subclass of Visitor.
More coupling?
Theres a lot more code here, and theres definite coupling between the
Trash hierarchy and the Visitor hierarchy. However, theres also high
cohesion within the respective sets of classes: they each do only one thing
(Trash describes Trash, while Visitor describes actions performed on
Trash), which is an indicator of a good design. Of course, in this case it
works well only if youre adding new Visitors, but it gets in the way when
you add new types of Trash.
Low coupling between classes and high cohesion within a class is
definitely an important design goal. Applied mindlessly, though, it can
prevent you from achieving a more elegant design. It seems that some
classes inevitably have a certain intimacy with each other. These often
occur in pairs that could perhaps be called couplets; for example,
containers and iterators. The Trash-Visitor pair above appears to be
another such couplet.
if(t.containsKey(type))
((List)t.get(type)).add(o);
else {
List v = new ArrayList();
v.add(o);
t.put(type,v);
}
}
public List get(Class type) {
return (List)t.get(type);
}
public Iterator keys() {
return t.keySet().iterator();
}
}
// Adapter class to allow callbacks
// from ParseTrash.fillBin():
class TypeMapAdapter implements Fillable {
TypeMap map;
public TypeMapAdapter(TypeMap tm) { map = tm; }
public void addTrash(Trash t) { map.add(t); }
}
public class DynaTrash extends UnitTest {
TypeMap bin = new TypeMap();
public DynaTrash() {
ParseTrash.fillBin("../trash/Trash.dat",
new TypeMapAdapter(bin));
}
public void test() {
Iterator keys = bin.keys();
while(keys.hasNext())
Trash.sumValue(
bin.get((Class)keys.next()).iterator());
}
public static void main(String args[]) {
new DynaTrash().test();
}
} ///:~
Although powerful, the definition for TypeMap is simple. It contains a
HashMap, and the add( ) method does most of the work. When you
add( ) a new object, the reference for the Class object for that type is
extracted. This is used as a key to determine whether an ArrayList
Summary
Coming up with a design such as TrashVisitor.java that contains a
larger amount of code than the earlier designs can seem at first to be
counterproductive. It pays to notice what youre trying to accomplish with
various designs. Design patterns in general strive to separate the things
that change from the things that stay the same. The things that change
can refer to many different kinds of changes. Perhaps the change occurs
because the program is placed into a new environment or because
something in the current environment changes (this could be: The user
wants to add a new shape to the diagram currently on the screen). Or, as
in this case, the change could be the evolution of the code body. While
previous versions of the trash sorting example emphasized the addition of
new types of Trash to the system, TrashVisitor.java allows you to
easily add new functionality without disturbing the Trash hierarchy.
Theres more code in TrashVisitor.java, but adding new functionality
to Visitor is cheap. If this is something that happens a lot, then its worth
the extra effort and code to make it happen more easily.
The discovery of the vector of change is no trivial matter; its not
something that an analyst can usually detect before the program sees its
initial design. The necessary information will probably not appear until
later phases in the project: sometimes only at the design or
implementation phases do you discover a deeper or more subtle need in
your system. In the case of adding new types (which was the focus of most
of the recycle examples) you might realize that you need a particular
inheritance hierarchy only when you are in the maintenance phase and
you begin extending the system!
One of the most important things that youll learn by studying design
patterns seems to be an about-face from what has been promoted so far in
this book. That is: OOP is all about polymorphism. This statement can
produce the two-year-old with a hammer syndrome (everything looks
like a nail). Put another way, its hard enough to get polymorphism, and
once you do, you try to cast all your designs into that one particular mold.
What design patterns say is that OOP isnt just about polymorphism. Its
about separating the things that change from the things that stay the
same. Polymorphism is an especially important way to do this, and it
turns out to be helpful if the programming language directly supports
polymorphism (so you dont have to wire it in yourself, which would tend
to make it prohibitively expensive). But design patterns in general show
other ways to accomplish the basic goal, and once your eyes have been
opened to this you will begin to search for more creative designs.
Since the Design Patterns book came out and made such an impact,
people have been searching for other patterns. You can expect to see more
of these appear as time goes on. Here are some sites recommended by
Jim Coplien, of C++ fame (http://www.bell-labs.com/~cope), who is one
of the main proponents of the patterns movement:
http://st-www.cs.uiuc.edu/users/patterns
http://c2.com/cgi/wiki
http://c2.com/ppr
http://www.bell-labs.com/people/cope/Patterns/Process/index.html
http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns
http://st-www.cs.uiuc.edu/cgi-bin/wikic/wikic
http://www.cs.wustl.edu/~schmidt/patterns.html
http://www.espinc.com/patterns/overview.html
Also note there has been a yearly conference on design patterns, called
PLOP, that produces a published proceedings, the third of which came
out in late 1997 (all published by Addison-Wesley).
Exercises
1.
2.
3.
13: Projects
A number of more challenging projects for you to solve.
[[Some of these may turn into examples in the book, and
so at some point might disappear from here]]
System.exit(0);
}
Maze m = new Maze();
m.load(args[0]);
Frame f = new Frame();
f.setSize(m.width*20, m.height*20);
f.add(m);
Rat r = new Rat(m, 0, 0);
f.setVisible(true);
}
public Maze() {
lines = new Vector();
setBackground(Color.lightGray);
}
synchronized public boolean
isEmptyXY(int x, int y) {
if (x < 0) x += width;
if (y < 0) y += height;
// Use mod arithmetic to bring rat in line:
byte[] by =
(byte[])(lines.elementAt(y%height));
return by[x%width]==' ';
}
synchronized public void
setXY(int x, int y, byte newByte) {
if (x < 0) x += width;
if (y < 0) y += height;
byte[] by =
(byte[])(lines.elementAt(y%height));
by[x%width] = newByte;
repaint();
}
public void
load(String filename) throws IOException {
String currentLine = null;
BufferedReader br = new BufferedReader(
new FileReader(filename));
for(currentLine = br.readLine();
currentLine != null;
currentLine = br.readLine()) {
lines.addElement(currentLine.getBytes());
if(width < 0 ||
currentLine.getBytes().length > width)
width = currentLine.getBytes().length;
}
height = lines.size();
br.close();
}
public void update(Graphics g) { paint(g); }
public void paint (Graphics g) {
int canvasHeight = this.getBounds().height;
int canvasWidth = this.getBounds().width;
if (height < 1 || width < 1)
return; // nothing to do
int width =
((byte[])(lines.elementAt(0))).length;
for (int y = 0; y < lines.size(); y++) {
byte[] b;
b = (byte[])(lines.elementAt(y));
for (int x = 0; x < width; x++) {
switch(b[x]) {
case ' ': // empty part of maze
g.setColor(Color.lightGray);
g.fillRect(
x*(canvasWidth/width),
y*(canvasHeight/height),
canvasWidth/width,
canvasHeight/height);
break;
case '*':
// a wall
g.setColor(Color.darkGray);
g.fillRect(
x*(canvasWidth/width),
y*(canvasHeight/height),
(canvasWidth/width)-1,
(canvasHeight/height)-1);
break;
default:
// must be rat
g.setColor(Color.red);
g.fillOval(x*(canvasWidth/width),
y*(canvasHeight/height),
canvasWidth/width,
canvasHeight/height);
break;
}
}
}
}
} ///:~
//: c13:Rat.java
public class Rat {
static int ratCount = 0;
private Maze prison;
private int vertDir = 0;
private int horizDir = 0;
private int x,y;
private int myRatNo = 0;
public Rat(Maze maze, int xStart, int yStart) {
myRatNo = ratCount++;
System.out.println("Rat no." + myRatNo +
" ready to scurry.");
prison = maze;
x = xStart;
y = yStart;
prison.setXY(x,y, (byte)'R');
new Thread() {
public void run(){ scurry(); }
}.start();
}
public void scurry() {
// Try and maintain direction if possible.
// Horizontal backward
boolean ratCanMove = true;
while(ratCanMove) {
ratCanMove = false;
// South
if (prison.isEmptyXY(x, y + 1)) {
vertDir = 1; horizDir = 0;
ratCanMove = true;
}
// North
if (prison.isEmptyXY(x, y - 1))
if (ratCanMove)
new Rat(prison, x, y-1);
// Rat can move already, so give
// this choice to the next rat.
else {
vertDir = -1; horizDir = 0;
ratCanMove = true;
}
// West
if (prison.isEmptyXY(x-1, y))
if (ratCanMove)
new Rat(prison, x-1, y);
// Rat can move already, so give
// this choice to the next rat.
else {
vertDir = 0; horizDir = -1;
ratCanMove = true;
}
// East
if (prison.isEmptyXY(x+1, y))
if (ratCanMove)
new Rat(prison, x+1, y);
// Rat can move already, so give
// this choice to the next rat.
else {
vertDir = 0; horizDir = 1;
ratCanMove = true;
}
if (ratCanMove) { // Move original rat.
x += horizDir;
y += vertDir;
prison.setXY(x,y,(byte)'R');
} // If not then the rat will die.
try {
Thread.sleep(2000);
} catch(InterruptedException ie) {}
}
System.out.println("Rat no." + myRatNo +
" can't move..dying..aarrgggh.");
}
} ///:~
The maze initialization file:
//:! c13:Amaze.txt
* **
* * **
*
***
* *******
* ****
***
***
*****
**********
*****
* * * * ** ** * * * ** *
* * * * ** * * * * **
*
**
*
**
* **
* ** * **
* **
*** * *** ***** * *** **
*
*
* *
*
*
* ** * *
///:~
* ** * *
XML Decorator
Create a pair of decorators for I/O Readers and Writers that encode (for
the Writer decorator) and decode (for the reader decorator) XML