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:
When a surrogate object is created, it is
given an implementation to which to send all of the method
calls.
Structurally, the difference between
Proxy and State is simple: a Proxy has only one
implementation, while State has more than one. The application of the
patterns is considered (in Design Patterns) to be distinct: Proxy
is used to control access to its implementation, while State allows you
to change the implementation dynamically. However, if you expand your notion of
“controlling access to implementation” then the two fit neatly
together.
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();
}
class Proxy implements ProxyBase {
private ProxyBase implementation;
public Proxy() {
implementation = new Implementation();
}
// Pass method calls to the implementation:
public void f() { implementation.f(); }
public void g() { implementation.g(); }
public void h() { implementation.h(); }
}
class Implementation implements ProxyBase {
public void f() {
System.out.println("Implementation.f()");
}
public void g() {
System.out.println("Implementation.g()");
}
public void h() {
System.out.println("Implementation.h()");
}
}
public class ProxyDemo extends UnitTest {
Proxy p = new Proxy();
public void test() {
// This just makes sure it will complete
// without throwing an exception.
p.f();
p.g();
p.h();
}
public static void main(String args[]) {
new ProxyDemo().test();
}
} ///:~
Of course, it isn’t
necessary that Implementation have the same interface as Proxy; as
long as Proxy is somehow “speaking for” the class that it is
referring method calls to then the basic idea is satisfied (note that this
statement is at odds with the definition for Proxy in GoF). However, it is
convenient to have a common interface so that Implementation is forced to
fulfill all the methods that Proxy needs to
call.
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:
- Remote
proxy. This proxies for an object in a different address space. A remote
proxy is created for you automatically by the RMI compiler rmic as it
creates stubs and
skeletons.
- Virtual
proxy. This provides “lazy initialization” to create expensive
objects on
demand.
- Protection
proxy. Used when you don’t want the client programmer to have full
access to the proxied
object.
- Smart
reference. To add additional actions when the proxied object is accessed.
For example, or to keep track of the number of references that are held for a
particular object, in order to implement the copy-on-write idiom and
prevent object aliasing. A simpler example is keeping track of the number of
calls to a particular
method.
You could look at a
Java reference as a kind of protection proxy, since it controls access to the
actual object on the heap (and ensures, for example, that you don’t use a
null reference).
[[ Rewrite this: In Design
Patterns, Proxy and State are not seen as related to each
other because the two are given (what I consider arbitrarily) different
structures. State, in particular, uses a separate implementation
hierarchy but this seems to me to be unnecessary unless you have decided that
the implementation is not under your control (certainly a possibility, but if
you own all the code there seems to be no reason not to benefit from the
elegance and helpfulness of the single base class). In addition, Proxy
need not use the same base class for its implementation, as long as the proxy
object is controlling access to the object it “fronting” for.
Regardless of the specifics, in both Proxy and State a surrogate
is passing method calls through to an implementation
object.]]]
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 it’s 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();
}
}
} ///:~
I’ve 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 State’s 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, I’ll use a fancy mousetrap that can move through
several states in the process of trapping a
mouse[9]. 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");
} ///:~
You’ll 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 won’t 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, it’s now
possible to create the first version of the mousetrap program. Each State
subclass defines it’s 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(
"Waiting: Broadcasting cheese smell");
}
public State next(Input i) {
MouseAction ma = (MouseAction)i;
if(ma.equals(MouseAction.appears))
return MouseTrap.luring;
return MouseTrap.waiting;
}
}
class Luring implements State {
public void run() {
System.out.println(
"Luring: Presenting Cheese, door open");
}
public State next(Input i) {
MouseAction ma = (MouseAction)i;
if(ma.equals(MouseAction.runsAway))
return MouseTrap.waiting;
if(ma.equals(MouseAction.enters))
return MouseTrap.trapping;
return MouseTrap.luring;
}
}
class Trapping implements State {
public void run() {
System.out.println("Trapping: Closing door");
}
public State next(Input i) {
MouseAction ma = (MouseAction)i;
if(ma.equals(MouseAction.escapes))
return MouseTrap.waiting;
if(ma.equals(MouseAction.trapped))
return MouseTrap.holding;
return MouseTrap.trapping;
}
}
class Holding implements State {
public void run() {
System.out.println("Holding: Mouse caught");
}
public State next(Input i) {
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 generates cyclic
initialization dependencies. To solve the problem, I’ve had to delay the
initialization of the tables until the first time that the next( )
method is called for a particular State object. Initially, the
next( ) methods can appear a little strange because of
this.
The StateT class is an
implementation of State (so that the same StateMachine class can
be used from the previous example) that adds a Map and a method to
initialize the map from a two-dimensional array. The next( ) method
has a base-class implementation which must be called from the overridden derived
class next( ) methods after they test for a null Map (and
initialize it if it’s null):
//: c04:mousetrap2:MouseTrap2Test.java
// A better mousetrap using tables
package c04.mousetrap2;
import c04.mouse.*;
import c04.statemachine.*;
import java.util.*;
import java.io.*;
import com.bruceeckel.util.*;
import com.bruceeckel.test.*;
abstract class StateT implements State {
protected Map transitions = null;
protected void init(Object[][] states) {
transitions = new HashMap();
for(int i = 0; i < states.length; i++)
transitions.put(states[i][0], states[i][1]);
}
public abstract void run();
public State next(Input i) {
if(transitions.containsKey(i))
return (State)transitions.get(i);
else
throw new RuntimeException(
"Input not supported for current state");
}
}
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
}
}
class Waiting extends StateT {
public void run() {
System.out.println(
"Waiting: Broadcasting cheese smell");
}
public State next(Input i) {
// Delayed initialization:
if(transitions == null)
init(new Object[][] {
{ MouseAction.appears, MouseTrap.luring },
});
return super.next(i);
}
}
class Luring extends StateT {
public void run() {
System.out.println(
"Luring: Presenting Cheese, door open");
}
public State next(Input i) {
if(transitions == null)
init(new Object[][] {
{ MouseAction.enters, MouseTrap.trapping },
{ MouseAction.runsAway, MouseTrap.waiting },
});
return super.next(i);
}
}
class Trapping extends StateT {
public void run() {
System.out.println("Trapping: Closing door");
}
public State next(Input i) {
if(transitions == null)
init(new Object[][] {
{ MouseAction.escapes, MouseTrap.waiting },
{ 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 it’s
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. Here’s what it
looks like:
(Simple State Machine
Diagram)
Goals:
- translation of state
diagram
- Vector of
change: the state diagram
representation
- implementation
- excess
of states (you could represent every single change with a new
state)
- and
flexibility
Observations:
- States are trivial –
no information or functions/data, just an
identity
- like the State
pattern!
- machine governs the move from state to
state
- to
flyweight
- state may move to many
others
- & action functions must also be
external to states
- description in a single table
containing all variations, for ease of
configuration
Example:
- Machine & Table-Driven
Code
- a vending
machine
- several other
patterns
- common state-machine code from specific
application (like template method)
- input causes a
seek for appropriate solution (like chain of
responsibility)
- and transitions are encapsulated
in function objects (objects that hold
functions)
- Java
constraint: methods are not first-class
objects
The State class
The State class is distinctly
different from before, since it is really just a placeholder with a name. Thus
it is not inherited from previous State classes:
//: c04:statemachine2:State.java
package c04.statemachine2;
public class State {
private String name;
public State(String nm) { name = nm; }
public String toString() { return name; }
} ///:~
Conditions for transition
In the state transition diagram, an input
is tested to see if it meets the condition necessary to transfer to the state
under question. As before, the Input is just a tagging
interface:
//: c04:statemachine2:Input.java
// Inputs to a state machine
package c04.statemachine2;
public interface Input {} ///:~
The
Condition evaluates the Input to decide whether this row in the
table is the correct transition:
//: c04:statemachine2:Condition.java
// Condition function object for state machine
package c04.statemachine2;
public interface Condition {
boolean condition(Input i);
} ///:~
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},
...
}
The basic machine
//: c04:statemachine2:StateMachine.java
// A table-driven state machine
package c04.statemachine2;
import java.util.*;
public class StateMachine {
private State state;
private Map map = new HashMap();
public StateMachine(State initial) {
state = initial;
}
public void buildTable(Object[][][] table) {
for(int i = 0; i < table.length; i++) {
Object[][] row = table[i];
Object currentState = row[0][0];
List transitions = new ArrayList();
for(int j = 1; j < row.length; j++)
transitions.add(row[j]);
map.put(currentState, transitions);
}
}
public void nextState(Input input) {
Iterator it=((List)map.get(state)).iterator();
while(it.hasNext()) {
Object[] tran = (Object[])it.next();
if(input == tran[0] ||
input.getClass() == tran[0]) {
if(tran[1] != null) {
Condition c = (Condition)tran[1];
if(!c.condition(input))
continue; // Failed test
}
if(tran[2] != null)
((Transition)tran[2]).transition(input);
state = (State)tran[3];
return;
}
}
throw new RuntimeException(
"Input not supported for current state");
}
} ///:~
Simple vending machine
//: c04:vendingmachine:VendingMachine.java
// Demonstrates use of StateMachine.java
package c04.vendingmachine;
import c04.statemachine2.*;
final class VM extends State {
private VM(String nm) { super(nm); }
public final static VM
quiescent = new VM("Quiesecent"),
collecting = new VM("Collecting"),
selecting = new VM("Selecting"),
unavailable = new VM("Unavailable"),
wantMore = new VM("Want More?"),
noChange = new VM("Use Exact Change Only"),
makesChange = new VM("Machine makes change");
}
final class HasChange implements Input {
private String name;
private HasChange(String nm) { name = nm; }
public String toString() { return name; }
public final static HasChange
yes = new HasChange("Has change"),
no = new HasChange("Cannot make change");
}
class ChangeAvailable extends StateMachine {
public ChangeAvailable() {
super(VM.makesChange);
buildTable(new Object[][][]{
{ {VM.makesChange}, // Current state
// Input, test, transition, next state:
{HasChange.no, null, null, VM.noChange}},
{ {VM.noChange}, // Current state
// Input, test, transition, next state:
{HasChange.yes, null,
null, VM.makesChange}},
});
}
}
final class Money implements Input {
private String name;
private int value;
private Money(String nm, int val) {
name = nm;
value = val;
}
public String toString() { return name; }
public int getValue() { return value; }
public final static Money
quarter = new Money("Quarter", 25),
dollar = new Money("Dollar", 100);
}
final class Quit implements Input {
private Quit() {}
public String toString() { return "Quit"; }
public final static Quit quit = new Quit();
}
final class FirstDigit implements Input {
private String name;
private int value;
private FirstDigit(String nm, int val) {
name = nm;
value = val;
}
public String toString() { return name; }
public int getValue() { return value; }
public final static FirstDigit
A = new FirstDigit("A", 0),
B = new FirstDigit("B", 1),
C = new FirstDigit("C", 2),
D = new FirstDigit("D", 3);
}
final class SecondDigit implements Input {
private String name;
private int value;
private SecondDigit(String nm, int val) {
name = nm;
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;
}
};
Condition itemNotAvailable = new Condition() {
public boolean condition(Input input) {
return !itemAvailable.condition(input);
//int i1 = first.getValue();
//int i2 = ((SecondDigit)input).getValue();
//return items[i1][i2].getQuantity() == 0;
}
};
Transition clearSelection = new Transition() {
public void transition(Input input) {
int i1 = first.getValue();
int i2 = ((SecondDigit)input).getValue();
ItemSlot is = items[i1][i2];
System.out.println(
"Clearing selection: item " + is +
" costs " + is.getPrice() +
" and has quantity " + is.getQuantity());
first = null;
}
};
Transition dispense = new Transition() {
public void transition(Input input) {
int i1 = first.getValue();
int i2 = ((SecondDigit)input).getValue();
ItemSlot is = items[i1][i2];
System.out.println("Dispensing item " +
is + " costs " + is.getPrice() +
" and has quantity " + is.getQuantity());
items[i1][i2].decrQuantity();
System.out.println("New Quantity " +
is.getQuantity());
amount -= is.getPrice();
System.out.println("Amount remaining " +
amount);
}
};
Transition showTotal = new Transition() {
public void transition(Input input) {
amount += ((Money)input).getValue();
System.out.println("Total amount = " +
amount);
}
};
Transition returnChange = new Transition() {
public void transition(Input input) {
System.out.println("Returning " + amount);
amount = 0;
}
};
Transition showDigit = new Transition() {
public void transition(Input input) {
first = (FirstDigit)input;
System.out.println("First Digit= "+ first);
}
};
public VendingMachine() {
super(VM.quiescent); // Initial state
for(int i = 0; i < items.length; i++)
for(int j = 0; j < items[i].length; j++)
items[i][j] = new ItemSlot((j+1)*25, 5);
items[3][0] = new ItemSlot(25, 0);
buildTable(new Object[][][]{
{ {VM.quiescent}, // Current state
// Input, test, transition, next state:
{Money.class, null,
showTotal, VM.collecting}},
{ {VM.collecting}, // Current state
// Input, test, transition, next state:
{Quit.quit, null,
returnChange, VM.quiescent},
{Money.class, null,
showTotal, VM.collecting},
{FirstDigit.class, null,
showDigit, VM.selecting}},
{ {VM.selecting}, // Current state
// Input, test, transition, next state:
{Quit.quit, null,
returnChange, VM.quiescent},
{SecondDigit.class, notEnough,
clearSelection, VM.collecting},
{SecondDigit.class, itemNotAvailable,
clearSelection, VM.unavailable},
{SecondDigit.class, itemAvailable,
dispense, VM.wantMore}},
{ {VM.unavailable}, // Current state
// Input, test, transition, next state:
{Quit.quit, null,
returnChange, VM.quiescent},
{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}},
});
}
} ///:~
Testing the machine
//: c04:vendingmachine:VendingMachineTest.java
// Demonstrates use of StateMachine.java
package c04.vendingmachine;
import c04.statemachine2.*;
import com.bruceeckel.test.*;
public class VendingMachineTest extends UnitTest{
VendingMachine vm = new VendingMachine();
Input[] inputs = {
Money.quarter,
Money.quarter,
Money.dollar,
FirstDigit.A,
SecondDigit.two,
FirstDigit.A,
SecondDigit.two,
FirstDigit.C,
SecondDigit.three,
FirstDigit.D,
SecondDigit.one,
Quit.quit,
};
public void test() {
for(int i = 0; i < inputs.length; i++)
vm.nextState(inputs[i]);
}
public static void main(String[] args) {
new VendingMachineTest().test();
}
} ///:~
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
- Create an example of the
“virtual
proxy.”
- Create
an example of the “Smart reference” proxy where you keep count of
the number of method calls to a particular
object.
- Create a
program similar to certain DBMS systems that only allow a certain number of
connections at any time. To implement this, use a singleton-like system that
controls the number of “connection” objects that it creates. When a
user is finished with a connection, the system must be informed so that it can
check that connection back in to be reused. To guarantee this, provide a proxy
object instead of a reference to the actual connection, and design the proxy so
that it will cause the connection to be released back to the
system.
- Using the
State, make a class called UnpredictablePerson which
changes the kind of response to its hello( ) method depending on
what kind of Mood it’s in. Add an additional kind of Mood
called
Prozac.
- Create
a simple copy-on write
implementation.
- Apply
TransitionTable.java to the “Washer”
problem.
- Create a
StateMachine system whereby the current state along with input
information determines the next state that the system will be in. To do this,
each state must store a reference back to the proxy object (the state
controller) so that it can request the state change. Use a HashMap to
create a table of states, where the key 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.
- Modify the
previous exercise so that the state machine can be configured by
creating/modifying a single multi-dimensional
array.
- Modify the
“mood” exercise from the previous session so that it becomes a state
machine using
StateMachine.java
- Create
an elevator state machine system using
StateMachine.java
- Create
a heating/air-conditioning system using
StateMachine.java
- A
generator is an object that produces other objects, just like a factory,
except that the generator function doesn’t require any arguments. Create a
MouseMoveGenerator which produces correct MouseMove actions as
outputs each time the generator function is called (that is, the mouse must move
in the proper sequence, thus the possible moves are based on the previous move
– it’s another state machine). Add a method iterator( ) to
produce an iterator, but this method should take an int argument that
specifies the number of moves to produce before hasNext( ) returns
false.
[9] No
mice were harmed in the creation of this example.