Java/Design Pattern/State Pattern
Dynamically changing the behavior of an object via composition (the State design pattern)
<source lang="java">
// : c07:Transmogrify.java // Dynamically changing the behavior of an object via composition (the State design pattern). // From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002 // www.BruceEckel.ru. See copyright notice in CopyRight.txt. abstract class Actor {
public abstract void act();
} class HappyActor extends Actor {
public void act() { System.out.println("HappyActor"); }
} class SadActor extends Actor {
public void act() { System.out.println("SadActor"); }
} class Stage {
private Actor actor = new HappyActor(); public void change() { actor = new SadActor(); } public void performPlay() { actor.act(); }
} public class Transmogrify {
public static void main(String[] args) { Stage stage = new Stage(); stage.performPlay(); stage.change(); stage.performPlay(); }
} ///:~
</source>
State Pattern in Java
<source lang="java">
//[C] 2002 Sun Microsystems, Inc.--- import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.AbstractTableModel; public class RunStatePattern {
public static void main(String[] arguments) { System.out.println("Example for the State pattern"); System.out.println(); if (!(new File("appointments.ser").exists())) { DataCreator.serialize("appointments.ser"); } System.out.println("Creating CalendarEditor"); CalendarEditor appointmentBook = new CalendarEditor(); System.out.println(""); System.out.println("Created. Appointments:"); System.out.println(appointmentBook.getAppointments()); System.out.println("Created. Creating GUI:"); StateGui application = new StateGui(appointmentBook); application.createGui(); System.out.println(""); }
} interface State {
public void save(); public void edit();
} interface Contact extends Serializable {
public static final String SPACE = " "; public String getFirstName(); public String getLastName(); public String getTitle(); public String getOrganization(); public void setFirstName(String newFirstName); public void setLastName(String newLastName); public void setTitle(String newTitle); public void setOrganization(String newOrganization);
} class ContactImpl implements Contact {
private String firstName; private String lastName; private String title; private String organization; public ContactImpl() { } public ContactImpl(String newFirstName, String newLastName, String newTitle, String newOrganization) { firstName = newFirstName; lastName = newLastName; title = newTitle; organization = newOrganization; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public String getTitle() { return title; } public String getOrganization() { return organization; } public void setFirstName(String newFirstName) { firstName = newFirstName; } public void setLastName(String newLastName) { lastName = newLastName; } public void setTitle(String newTitle) { title = newTitle; } public void setOrganization(String newOrganization) { organization = newOrganization; } public String toString() { return firstName + SPACE + lastName; }
} interface Location extends Serializable {
public String getLocation(); public void setLocation(String newLocation);
} class LocationImpl implements Location {
private String location; public LocationImpl() { } public LocationImpl(String newLocation) { location = newLocation; } public String getLocation() { return location; } public void setLocation(String newLocation) { location = newLocation; } public String toString() { return location; }
} class FileLoader {
public static Object loadData(File inputFile) { Object returnValue = null; try { if (inputFile.exists()) { if (inputFile.isFile()) { ObjectInputStream readIn = new ObjectInputStream( new FileInputStream(inputFile)); returnValue = readIn.readObject(); readIn.close(); } else { System.err.println(inputFile + " is a directory."); } } else { System.err.println("File " + inputFile + " does not exist."); } } catch (ClassNotFoundException exc) { exc.printStackTrace(); } catch (IOException exc) { exc.printStackTrace(); } return returnValue; } public static void storeData(File outputFile, Serializable data) { try { ObjectOutputStream writeOut = new ObjectOutputStream( new FileOutputStream(outputFile)); writeOut.writeObject(data); writeOut.close(); } catch (IOException exc) { exc.printStackTrace(); } }
} class DataCreator {
private static final String DEFAULT_FILE = "data.ser"; private static Calendar dateCreator = Calendar.getInstance(); public static void main(String[] args) { String fileName; if (args.length == 1) { fileName = args[0]; } else { fileName = DEFAULT_FILE; } serialize(fileName); } public static void serialize(String fileName) { try { serializeToFile(createData(), fileName); } catch (IOException exc) { exc.printStackTrace(); } } private static Serializable createData() { ArrayList appointments = new ArrayList(); ArrayList contacts = new ArrayList(); contacts.add(new ContactImpl("Test", "Subject", "Volunteer", "United Patterns Consortium")); Location location1 = new LocationImpl("Punxsutawney, PA"); appointments.add(new Appointment("Slowpokes anonymous", contacts, location1, createDate(2001, 1, 1, 12, 01), createDate(2001, 1, 1, 12, 02))); appointments.add(new Appointment("Java focus group", contacts, location1, createDate(2001, 1, 1, 12, 30), createDate(2001, 1, 1, 14, 30))); appointments .add(new Appointment("Something else", contacts, location1, createDate(2001, 1, 1, 12, 01), createDate(2001, 1, 1, 12, 02))); appointments.add(new Appointment("Yet another thingie", contacts, location1, createDate(2001, 1, 1, 12, 01), createDate(2001, 1, 1, 12, 02))); return appointments; } private static void serializeToFile(Serializable content, String fileName) throws IOException { ObjectOutputStream serOut = new ObjectOutputStream( new FileOutputStream(fileName)); serOut.writeObject(content); serOut.close(); } public static Date createDate(int year, int month, int day, int hour, int minute) { dateCreator.set(year, month, day, hour, minute); return dateCreator.getTime(); }
} class Appointment implements Serializable {
private String reason; private ArrayList contacts; private Location location; private Date startDate; private Date endDate; public Appointment(String reason, ArrayList contacts, Location location, Date startDate, Date endDate) { this.reason = reason; this.contacts = contacts; this.location = location; this.startDate = startDate; this.endDate = endDate; } public String getReason() { return reason; } public ArrayList getContacts() { return contacts; } public Location getLocation() { return location; } public Date getStartDate() { return startDate; } public Date getEndDate() { return endDate; } public void setReason(String reason) { this.reason = reason; } public void setContacts(ArrayList contacts) { this.contacts = contacts; } public void setLocation(Location location) { this.location = location; } public void setStartDate(Date startDate) { this.startDate = startDate; } public void setEndDate(Date endDate) { this.endDate = endDate; } public String toString() { return "Appointment:" + "\n Reason: " + reason + "\n Location: " + location + "\n Start: " + startDate + "\n End: " + endDate + "\n"; }
} class CalendarEditor {
private State currentState; private File appointmentFile; private ArrayList appointments = new ArrayList(); private static final String DEFAULT_APPOINTMENT_FILE = "appointments.ser"; public CalendarEditor() { this(DEFAULT_APPOINTMENT_FILE); } public CalendarEditor(String appointmentFileName) { appointmentFile = new File(appointmentFileName); try { appointments = (ArrayList) FileLoader.loadData(appointmentFile); } catch (ClassCastException exc) { System.err .println("Unable to load information. The file does not contain a list of appointments."); } currentState = new CleanState(); } public void save() { currentState.save(); } public void edit() { currentState.edit(); } private class DirtyState implements State { private State nextState; public DirtyState(State nextState) { this.nextState = nextState; } public void save() { FileLoader.storeData(appointmentFile, appointments); currentState = nextState; } public void edit() { } } private class CleanState implements State { private State nextState = new DirtyState(this); public void save() { } public void edit() { currentState = nextState; } } public ArrayList getAppointments() { return appointments; } public void addAppointment(Appointment appointment) { if (!appointments.contains(appointment)) { appointments.add(appointment); } } public void removeAppointment(Appointment appointment) { appointments.remove(appointment); }
} class StateGui implements ActionListener {
private JFrame mainFrame; private JPanel controlPanel, editPanel; private CalendarEditor editor; private JButton save, exit; public StateGui(CalendarEditor edit) { editor = edit; } public void createGui() { mainFrame = new JFrame("State Pattern Example"); Container content = mainFrame.getContentPane(); content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); editPanel = new JPanel(); editPanel.setLayout(new BorderLayout()); JTable appointmentTable = new JTable(new StateTableModel( (Appointment[]) editor.getAppointments().toArray( new Appointment[1]))); editPanel.add(new JScrollPane(appointmentTable)); content.add(editPanel); controlPanel = new JPanel(); save = new JButton("Save Appointments"); exit = new JButton("Exit"); controlPanel.add(save); controlPanel.add(exit); content.add(controlPanel); save.addActionListener(this); exit.addActionListener(this); mainFrame.addWindowListener(new WindowCloseManager()); mainFrame.pack(); mainFrame.setVisible(true); } public void actionPerformed(ActionEvent evt) { Object originator = evt.getSource(); if (originator == save) { saveAppointments(); } else if (originator == exit) { exitApplication(); } } private class WindowCloseManager extends WindowAdapter { public void windowClosing(WindowEvent evt) { exitApplication(); } } private void saveAppointments() { editor.save(); } private void exitApplication() { System.exit(0); } private class StateTableModel extends AbstractTableModel { private final String[] columnNames = { "Appointment", "Contacts", "Location", "Start Date", "End Date" }; private Appointment[] data; public StateTableModel(Appointment[] appointments) { data = appointments; } public String getColumnName(int column) { return columnNames[column]; } public int getRowCount() { return data.length; } public int getColumnCount() { return columnNames.length; } public Object getValueAt(int row, int column) { Object value = null; switch (column) { case 0: value = data[row].getReason(); break; case 1: value = data[row].getContacts(); break; case 2: value = data[row].getLocation(); break; case 3: value = data[row].getStartDate(); break; case 4: value = data[row].getEndDate(); break; } return value; } public boolean isCellEditable(int row, int column) { return ((column == 0) || (column == 2)) ? true : false; } public void setValueAt(Object value, int row, int column) { switch (column) { case 0: data[row].setReason((String) value); editor.edit(); break; case 1: break; case 2: data[row].setLocation(new LocationImpl((String) value)); editor.edit(); break; case 3: break; case 4: break; } } }
}
</source>
State pattern in Java 2
<source lang="java">
/* The Design Patterns Java Companion Copyright (C) 1998, by James W. Cooper IBM Thomas J. Watson Research Center
- /
import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Vector; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JToggleButton; import javax.swing.JToolBar; public class StateDraw extends JFrame implements ActionListener {
JToolBar tbar; Mediator med; public StateDraw() { super("State Drawing"); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JPanel jp = new JPanel(); getContentPane().add(jp); med = new Mediator(); jp.setLayout(new BorderLayout()); tbar = new JToolBar(); tbar.setFloatable(false); jp.add("North", tbar); PickButton pick = new PickButton(this, med); tbar.add(pick); tbar.addSeparator(); RectButton rect = new RectButton(this, med); tbar.add(rect); FillButton fill = new FillButton(this, med); tbar.add(fill); CircleButton circ = new CircleButton(this, med); tbar.add(circ); tbar.addSeparator(); ClearButton clr = new ClearButton(this, med); tbar.add(clr); JCanvas canvas = new JCanvas(med); jp.add("Center", canvas); MouseApp map = new MouseApp(med); canvas.addMouseListener(map); MouseMoveApp mvap = new MouseMoveApp(med); canvas.addMouseMotionListener(mvap); setSize(new Dimension(400, 300)); setVisible(true); } public void actionPerformed(ActionEvent e) { Command comd = (Command) e.getSource(); comd.Execute(); } static public void main(String[] argv) { new StateDraw(); }
}
class MouseApp extends MouseAdapter {
Mediator med; public MouseApp(Mediator md) { super(); med = md; } public void mousePressed(MouseEvent e) { med.mouseDown(e.getX(), e.getY()); } public void mouseReleased(MouseEvent e) { med.mouseUp(e.getX(), e.getY()); }
} class MouseMoveApp extends MouseMotionAdapter {
Mediator med; public MouseMoveApp(Mediator md) { super(); med = md; } public void mouseDragged(MouseEvent e) { med.mouseDrag(e.getX(), e.getY()); }
} class ArrowState extends State {
private Mediator med; public ArrowState(Mediator md) { med = md; } public void mouseDown(int x, int y) { Vector drawings = med.getDrawings(); for (int i = 0; i < drawings.size(); i++) { Drawing d = (Drawing) drawings.elementAt(i); if (d.contains(x, y)) med.setSelected(d); } }
} class CircleState extends State {
private Mediator med; //save Mediator public CircleState(Mediator md) { med = md; } //Draw circle where mouse clicks public void mouseDown(int x, int y) { med.addDrawing(new visCircle(x, y)); }
} class ClearButton extends JButton implements Command {
Mediator med; public ClearButton(ActionListener act, Mediator md) { super("C"); setToolTipText("Clear"); addActionListener(act); med = md; } public void Execute() { med.clear(); }
} class CircleButton extends JToggleButton implements Command {
Mediator med; public CircleButton(ActionListener act, Mediator md) { super(" "); setSize(new Dimension(35, 35)); setBorderPainted(true); setMargin(new Insets(5, 12, 5, 12)); setToolTipText("Draw circle"); addActionListener(act); med = md; med.registerCircleButton(this); } public Dimension getPreferredSize() { return new Dimension(35, 35); } public void paint(Graphics g) { super.paint(g); int h = getHeight(); int w = getWidth(); g.setColor(Color.black); g.drawArc(2, 2, h - 4, h - 4, 0, 360); } public void Execute() { med.startCircle(); }
} class State {
public void mouseDown(int x, int y) { } public void mouseUp(int x, int y) { } public void mouseDrag(int x, int y) { } public void select(Drawing d, Color c) { }
} interface Command {
public void Execute();
} class FillState extends State {
private Mediator med; //save Mediator private Color color; //save current color public FillState(Mediator md) { med = md; } //Fill drawing if selected public void select(Drawing d, Color c) { color = c; if (d != null) { d.setFill(c); //fill that drawing } } //Fill drawing if you click inside one public void mouseDown(int x, int y) { Vector drawings = med.getDrawings(); for (int i = 0; i < drawings.size(); i++) { Drawing d = (Drawing) drawings.elementAt(i); if (d.contains(x, y)) d.setFill(color); //fill drawing } }
} class JCanvas extends JPanel {
Mediator med; public JCanvas(Mediator md) { med = md; med.registerCanvas(this); setBackground(Color.white); } public void paint(Graphics g) { super.paint(g); med.reDraw(g); }
} class Mediator {
boolean startRect; boolean dSelected; Vector drawings; Vector undoList; RectButton rectButton; FillButton fillButton; CircleButton circButton; PickButton arrowButton; JPanel canvas; Drawing selectedDrawing; StateManager stMgr; public Mediator() { startRect = false; dSelected = false; drawings = new Vector(); undoList = new Vector(); stMgr = new StateManager(this); } public void startRectangle() { stMgr.setRect(); arrowButton.setSelected(false); circButton.setSelected(false); fillButton.setSelected(false); } public void startCircle() { stMgr.setCircle(); rectButton.setSelected(false); arrowButton.setSelected(false); fillButton.setSelected(false); } public void startFill() { stMgr.setFill(); rectButton.setSelected(false); circButton.setSelected(false); arrowButton.setSelected(false); stMgr.select(selectedDrawing, Color.red); repaint(); } public void startArrow() { stMgr.setArrow(); rectButton.setSelected(false); circButton.setSelected(false); fillButton.setSelected(false); } public Drawing getSelected() { return selectedDrawing; } public void fillSelected() { if (dSelected) { selectedDrawing.setFill(Color.red); } } public Vector getDrawings() { return drawings; } public void addDrawing(Drawing d) { drawings.addElement(d); } public void registerRectButton(RectButton rb) { rectButton = rb; } public void registerCircleButton(CircleButton cb) { circButton = cb; } public void registerArrowButton(PickButton ab) { arrowButton = ab; } public void registerFillButton(FillButton fb) { fillButton = fb; } public void registerCanvas(JPanel p) { canvas = p; } public void mouseDown(int x, int y) { stMgr.mouseDown(x, y); repaint(); } public void mouseUp(int x, int y) { stMgr.mouseUp(x, y); } public void unpick() { dSelected = false; if (selectedDrawing != null) { selectedDrawing.setSelected(false); selectedDrawing = null; repaint(); } } public void rememberPosition() { if (dSelected) { //Memento m = new Memento(d); //undoList.addElement(m); } } public void setSelected(Drawing d) { if (d != null) { dSelected = true; selectedDrawing = d; d.setSelected(true); repaint(); } } public void pickRect(int x, int y) { } public void clear() { drawings = new Vector(); undoList = new Vector(); dSelected = false; selectedDrawing = null; repaint(); } private void repaint() { canvas.repaint(); } public void mouseDrag(int x, int y) { stMgr.mouseDrag(x, y); } public void reDraw(Graphics g) { g.setColor(Color.black); for (int i = 0; i < drawings.size(); i++) { Drawing v = (Drawing) drawings.elementAt(i); v.draw(g); } } public void undo() { if (undoList.size() > 0) { //get last element in undo list Object obj = undoList.lastElement(); undoList.removeElement(obj); //and remove it //if this is an Integer, the last action was a new rectangle if (obj instanceof Integer) { //remove last created rectangle Object drawObj = drawings.lastElement(); drawings.removeElement(drawObj); } //if this is a Memento, the last action was a move if (obj instanceof Memento) { //get the Memento Memento m = (Memento) obj; m.restore(); //and restore the old position } repaint(); } }
} class RectButton extends JToggleButton implements Command {
Mediator med; public RectButton(ActionListener act, Mediator md) { super(" "); setSize(new Dimension(35, 35)); setBorderPainted(true); setMargin(new Insets(5, 12, 5, 12)); setToolTipText("Draw rectangle"); addActionListener(act); med = md; med.registerRectButton(this); } public Dimension getPreferredSize() { return new Dimension(35, 35); } public void paint(Graphics g) { super.paint(g); int h = getHeight(); int w = getWidth(); g.setColor(Color.black); g.drawRect(4, 4, w - 8, h - 8); } public void Execute() { med.startRectangle(); }
} class UndoButton extends JButton implements Command {
Mediator med; public UndoButton(ActionListener act, Mediator md) { super("U"); //setSize(new Dimension(25,25)); setMargin(new Insets(5, 12, 5, 12)); setToolTipText("Undo"); addActionListener(act); med = md; } public void Execute() { med.undo(); }
} class FillButton extends JToggleButton implements Command {
Mediator med; public FillButton(ActionListener act, Mediator md) { super(" "); setSize(new Dimension(35, 35)); setBorderPainted(true); setMargin(new Insets(5, 12, 5, 12)); setToolTipText("Fill rectangle"); addActionListener(act); med = md; med.registerFillButton(this); } public Dimension getPreferredSize() { return new Dimension(35, 35); } public void paint(Graphics g) { super.paint(g); int h = getHeight(); int w = getWidth(); g.setColor(Color.black); g.drawRect(4, 4, w - 8, h - 8); g.setColor(Color.red); g.fillRect(4, 10, w - 8, h - 14); } public void Execute() { if (isSelected()) { med.startFill(); } }
} class Memento {
public Memento(Drawing d) { } public void restore() { }
} class RectState extends State {
private Mediator med; //save the Mediator here public RectState(Mediator md) { med = md; } //create a new Rectangle where mode clicks public void mouseDown(int x, int y) { med.addDrawing(new visRectangle(x, y)); }
} class StateManager {
private State currentState; RectState rState; ArrowState aState; CircleState cState; FillState fState; public StateManager(Mediator med) { rState = new RectState(med); cState = new CircleState(med); aState = new ArrowState(med); fState = new FillState(med); currentState = aState; } public void setRect() { currentState = rState; } public void setCircle() { currentState = cState; } public void setFill() { currentState = fState; } public void setArrow() { currentState = aState; } public void mouseDown(int x, int y) { currentState.mouseDown(x, y); } public void mouseUp(int x, int y) { currentState.mouseUp(x, y); } public void mouseDrag(int x, int y) { currentState.mouseDrag(x, y); } public void select(Drawing d, Color c) { currentState.select(d, c); }
} class Drawing {
protected int x, y, w, h; protected Rectangle rect; protected boolean selected; protected boolean filled; protected Color fillColor; //------------------------------------------- public void setSelected(boolean b) { selected = b; } public void draw(Graphics g) { } public void move(int xpt, int ypt) { x = xpt; y = ypt; } public boolean contains(int x, int y) { return rect.contains(x, y); } protected void saveAsRect() { rect = new Rectangle(x - w / 2, y - h / 2, w, h); } protected void setFill(Color c) { filled = true; fillColor = c; }
} class visCircle extends Drawing {
public visCircle(int xpt, int ypt) { x = xpt; y = ypt; w = 40; h = 30; saveAsRect(); } //------------------------------------------- public void draw(Graphics g) { g.drawArc(x, y, w, h, 0, 360); if (filled) { g.setColor(fillColor); g.fillArc(x, y, w, h, 0, 360); } if (selected) { g.setColor(Color.black); g.fillRect(x + w / 2, y - 2, 4, 4); g.fillRect(x - 2, y + h / 2, 4, 4); g.fillRect(x + w / 2, y + h - 2, 4, 4); g.fillRect(x + w - 2, y + h / 2, 4, 4); } }
} //=============================================== class circleMemento extends Memento {
visCircle circ; //saved fields- remember internal fields //of the specified visual rectangle int x, y, w, h; public circleMemento(visCircle r) { super(r); circ = r; x = circ.x; y = circ.y; w = circ.w; h = circ.h; } //------------------------------------------- public void restore() { //restore the internal state of //the specified rectangle circ.x = x; circ.y = y; circ.h = h; circ.w = w; }
} class visRectangle extends Drawing {
public visRectangle(int xpt, int ypt) { x = xpt; y = ypt; w = 40; h = 30; saveAsRect(); } //------------------------------------------- public void draw(Graphics g) { g.drawRect(x, y, w, h); if (filled) { g.setColor(fillColor); g.fillRect(x, y, w, h); } if (selected) { g.setColor(Color.black); g.fillRect(x + w / 2, y - 2, 4, 4); g.fillRect(x - 2, y + h / 2, 4, 4); g.fillRect(x + w / 2, y + h - 2, 4, 4); g.fillRect(x + w - 2, y + h / 2, 4, 4); } }
}
class rectMemento extends Memento {
visRectangle rect; //saved fields- remember internal fields //of the specified visual rectangle int x, y, w, h; public rectMemento(visRectangle r) { super(r); rect = r; x = rect.x; y = rect.y; w = rect.w; h = rect.h; } //------------------------------------------- public void restore() { //restore the internal state of //the specified rectangle rect.x = x; rect.y = y; rect.h = h; rect.w = w; }
} class PickButton extends JToggleButton implements Command {
Mediator med; public PickButton(ActionListener act, Mediator md) { super(new ImageIcon("arrow.gif")); setSize(new Dimension(35, 35)); setBorderPainted(true); setMargin(new Insets(0, 0, 0, 0)); setToolTipText("Select drawing element"); addActionListener(act); med = md; med.registerArrowButton(this); } //------------------------------------------- public void Execute() { med.startArrow(); }
}
</source>