The State Pattern
An Introduction with Examples in Java |
Prof. David Bernstein |
Computer Science Department |
bernstdh@jmu.edu |
if
statements
A Statechart Diagram for a Simplified Garage Door Opener
import java.io.*; /** * The Controller for a garage door opener * * @author Prof. David Bernstein, James Madison University * @version 0.1 */ public class Controller { private BufferedReader in; private PrintWriter out; private int state; private static final int AWAITING_COMBINATION = 0; private static final int CLOSED = 1; private static final int LOCKED = 2; private static final int OPENED = 3; /** * Explicit Value Constructor * * @param is The InputStream used for input and output * @param os The OutputStream used for input and output */ public Controller(InputStream is, OutputStream os) throws IOException { in = new BufferedReader(new InputStreamReader(is)); out = new PrintWriter(os); setState(CLOSED); } /** * Run this controller (i.e., prompt the user to enter * a command and respond accordingly) */ public void run() throws IOException { String line; out.print("Command (close,combination,error,lock,open,unlock): "); out.flush(); while ((line=in.readLine()) != null) { if (line.equals("close")) { if (state == OPENED) setState(CLOSED); } if (line.equals("combination")) { if (state == AWAITING_COMBINATION) setState(CLOSED); } if (line.equals("error")) { if (state == AWAITING_COMBINATION) setState(LOCKED); } if (line.equals("lock")) { if (state == CLOSED) setState(LOCKED); } if (line.equals("open")) { if (state == CLOSED) setState(OPENED); } if (line.equals("unlock")) { if (state == LOCKED) setState(AWAITING_COMBINATION); } out.print("Command (close,combination,error,lock,open,unlock): "); out.flush(); } } /** * Set the state * * @param state The new state */ private void setState(int state) { this.state = state; out.println(" State set to: "+state); out.flush(); } }
if
statements be
for this?State
State
classesContext
In UML:
Using the State Pattern
/** * An abstract State that the Controller can be in * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public abstract class State { /** * Process a close transition */ public void close() { } /** * Process a combinationEntered transition */ public void combinationEntered() { } /** * Enter this State * * Specifically, construct an instance if necessary and perform any * necessary operations * * @param controller The Controller to perform operations on * @return The (singleton) instance of this State */ public static State enter(Controller controller) { return null; } /** * Process an errorEntered transition */ public void errorEntered() { } /** * Process a lock transition */ public void lock() { } /** * Process a open transition */ public void open() { } /** * Process a startUnlock transition */ public void startUnlock() { } }
/** * The AwaitingCombination State (for a garage door Controller) * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class AwaitingCombination extends State { private Controller controller; private static AwaitingCombination instance; /** * Explicit Value Constuctor * [used by the enter() method] * * @param controller The Controller to use */ private AwaitingCombination(Controller controller) { this.controller = controller; } /** * Process a combinationEntered transition */ public void combinationEntered() { controller.changeState(Closed.enter(controller)); } /** * Enter this State * * Specifically, construct an instance if necessary and perform any * necessary operations * * @param controller The Controller to perform operations on * @return The (singleton) instance of this State */ public static State enter(Controller controller) { if (instance == null) instance = new AwaitingCombination(controller); controller.reset(); return instance; } /** * Process an errorEntered transition */ public void errorEntered() { controller.changeState(Locked.enter(controller)); } }
/** * The Closed State (for a garage door Controller) * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Closed extends State { private Controller controller; private static Closed instance; /** * Explicit Value Constuctor * [used by the enter() method] * * @param controller The Controller to use */ private Closed(Controller controller) { this.controller = controller; } /** * Enter this State * * Specifically, construct an instance if necessary and perform any * necessary operations * * @param controller The Controller to perform operations on * @return The (singleton) instance of this State */ public static State enter(Controller controller) { if (instance == null) instance = new Closed(controller); return instance; } /** * Process a lock transition */ public void lock() { controller.changeState(Locked.enter(controller)); } /** * Process an open transition */ public void open() { controller.changeState(Opened.enter(controller)); } }
/** * The Locked State (for a garage door Controller) * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Locked extends State { private Controller controller; private static Locked instance; /** * Explicit Value Constuctor * [used by the enter() method] * * @param controller The Controller to use */ private Locked(Controller controller) { this.controller = controller; } /** * Enter this State * * Specifically, construct an instance if necessary and perform any * necessary operations * * @param controller The Controller to perform operations on * @return The (singleton) instance of this State */ public static State enter(Controller controller) { if (instance == null) instance = new Locked(controller); return instance; } /** * Process a startUnlock transition */ public void startUnlock() { controller.changeState(AwaitingCombination.enter(controller)); } }
/** * The Opened State (for a garage door Controller) * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Opened extends State { private Controller controller; private static Opened instance; /** * Explicit Value Constuctor * [used by the enter() method] * * @param controller The Controller to use */ private Opened(Controller controller) { this.controller = controller; } /** * Process a close transition */ public void close() { controller.changeState(Closed.enter(controller)); } /** * Enter this State * * Specifically, construct an instance if necessary and perform any * necessary operations * * @param controller The Controller to perform operations on * @return The (singleton) instance of this State */ public static State enter(Controller controller) { if (instance == null) instance = new Opened(controller); return instance; } }
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * The Controller for a garage door opener * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Controller extends JFrame implements ActionListener { private JLabel display; private State state; private String entry; /** * Default Constructot */ public Controller() { super(); reset(); state = Closed.enter(this); performLayout(); } /** * Handle actionPerformed messages (required by ActionListener) * * @param evt The ActionEvent that generated the message */ public void actionPerformed(ActionEvent evt) { String ac; ac = evt.getActionCommand(); if (ac.equals("Close")) state.close(); else if (ac.equals("Lock")) state.lock(); else if (ac.equals("Open")) state.open(); else if (ac.equals("Unlock")) state.startUnlock(); else { entry += ac; System.out.println(entry); if (entry.length() == 4) { if (entry.equals("1337")) state.combinationEntered(); else state.errorEntered(); reset(); } } } /** * Change the State of the system * * @param state The new State */ public void changeState(State state) { this.state = state; setMessage(state.toString()); } /** * Reset the controller */ public void reset() { entry = ""; } /** * Layout this Controller */ private void performLayout() { JButton close, open, lock, unlock; JButton[] number; JPanel center, contentPane; number = new JButton[10]; contentPane = (JPanel)getContentPane(); contentPane.setLayout(new BorderLayout()); display = new JLabel("xDoor 1337", SwingConstants.CENTER); contentPane.add(display, BorderLayout.NORTH); center = new JPanel(); center.setLayout(new GridLayout(5,3)); contentPane.add(center, BorderLayout.CENTER); for (int i=1; i<=9; i++) { number[i] = new JButton(""+i); center.add(number[i]); } open = new JButton("Open"); center.add(open); number[0] = new JButton("0"); center.add(number[0]); close = new JButton("Close"); center.add(close); unlock = new JButton("Unlock"); center.add(unlock); center.add(new JLabel(" ")); lock = new JButton("Lock"); center.add(lock); for (int i=0; i<=9; i++) number[i].addActionListener(this); close.addActionListener(this); lock.addActionListener(this); open.addActionListener(this); unlock.addActionListener(this); } /** * Set the message on the display * * @param message The message */ public void setMessage(String message) { display.setText(message); } }