MindView Inc.
[ Viewing Hints ] [ Revision History ] [ Build the Code ] [ Report an Error ]
[ Free Newsletter ] [ Seminars ] [ Consulting ]

Thinking in Patterns with Java, Revision 0.6

©2001 by Bruce Eckel

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]

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:

  1. 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.
  2. Virtual proxy. This provides “lazy initialization” to create expensive objects on demand.
  3. Protection proxy. Used when you don’t want the client programmer to have full access to the proxied object.
  4. 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:

Observations:

Example:


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

  1. Create an example of the “virtual proxy.”
  2. Create an example of the “Smart reference” proxy where you keep count of the number of method calls to a particular object.
  3. 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.
  4. 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.
  5. Create a simple copy-on write implementation.
  6. Apply TransitionTable.java to the “Washer” problem.
  7. 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.
  8. Modify the previous exercise so that the state machine can be configured by creating/modifying a single multi-dimensional array.
  9. Modify the “mood” exercise from the previous session so that it becomes a state machine using StateMachine.java
  10. Create an elevator state machine system using StateMachine.java
  11. Create a heating/air-conditioning system using StateMachine.java
  12. 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.

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:09/08/2001