|
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);
}
}