State pattern
This article includes a list of references, related reading, or external links, but its sources remain unclear because it lacks inline citations. (April 2009) |
The state pattern is a behavioral software design pattern, also known as the objects for states pattern. This pattern is used in computer programming to represent the state of an object. This is a clean way for an object to partially change its type at runtime[1].
Pseudocode Example
Take, for example, a drawing program. The program has a mouse cursor, which at any point in time can be acting as one of several tools. Instead of switching between multiple cursor objects, the cursor maintains an internal state representing the tool currently in use. When a tool-dependent method is called (say, as a result of a mouse click), the method call is passed on to the cursor's state.
Each tool corresponds to a state. The shared abstract state class is AbstractTool:
class AbstractTool is function moveTo(point) is input: the location point the mouse moved to (this function must be implemented by subclasses) function mouseDown(point) is input: the location point the mouse is at (this function must be implemented by subclasses) function mouseUp(point) is input: the location point the mouse is at (this function must be implemented by subclasses)
According to this definition, each tool must handle movement of the mouse cursor and also the start and end of any click or drag.
Using that base class, simple pen and selection tools could look like this:
subclass PenTool of AbstractTool is last_mouse_position := invalid mouse_button := up function moveTo(point) is input: the location point the mouse moved to if mouse_button = down (draw a line from the last_mouse_position to point) last_mouse_position := point function mouseDown(point) is input: the location point the mouse is at mouse_button := down last_mouse_position := point function mouseUp(point) is input: the location point the mouse is at mouse_button := up subclass SelectionTool of AbstractTool is selection_start := invalid mouse_button := up function moveTo(point) is input: the location point the mouse moved to if mouse_button = up (select the rectangle between selection_start and point) function mouseDown(point) is input: the location point the mouse is at mouse_button := down selection_start := point function mouseUp(point) is input: the location point the mouse is at mouse_button := up
For this example, the class for the context is called Cursor. The methods named in the abstract state class (AbstractTool in this case) are also implemented in the context. In the context class, these methods invoke the corresponding method of the current state, represented by current_tool.
class Cursor is current_tool := new PenTool function moveTo(point) is input: the location point the mouse moved to current_tool.moveTo(point) function mouseDown(point) is input: the location point the mouse is at current_tool.mouseDown(point) function mouseUp(point) is input: the location point the mouse is at current_tool.mouseUp(point) function usePenTool() is current_tool := new PenTool function useSelectionTool() is current_tool := new SelectionTool
Notice how one Cursor object can act both as a PenTool and a SelectionTool at different points, by passing the appropriate method calls on to whichever tool is active. That is the essence of the state pattern. In this case, we could have combined state and object by creating PenCursor and SelectCursor classes, thus reducing the solution to simple inheritance, but in practice, Cursor may carry data which is expensive or inelegant to copy to a new object whenever a new tool is selected.
Examples
Java
The state interface and two implementations. The state method has a reference to the context object and is able to change its state.
interface State {
public void writeName(StateContext stateContext, String name);
}
class StateA implements State {
public void writeName(StateContext stateContext, String name) {
System.out.println(name.toLowerCase());
stateContext.setState(new StateB());
}
}
class StateB implements State {
private int count=0;
public void writeName(StateContext stateContext, String name){
System.out.println(name.toUpperCase());
if(++count>1) {
stateContext.setState(new StateA());
}
}
}
The context class has a state variable which it instantiates in an initial state, in this case StateA. In its method, it uses the corresponding methods of the state object.
public class StateContext {
private State myState;
public StateContext() {
setState(new StateA());
}
// normally only called by classes implementing the State interface
public void setState(State stateName) {
this.myState = stateName;
}
public void writeName(String name) {
this.myState.writeName(this, name);
}
}
And the usage:
public class TestClientState {
public static void main(String[] args) {
StateContext sc = new StateContext();
sc.writeName("Monday");
sc.writeName("Tuesday");
sc.writeName("Wednesday");
sc.writeName("Thursday");
sc.writeName("Saturday");
sc.writeName("Sunday");
}
}
Python
In the python example below, explicit declaration of an abstract state class is unnecessary. The imported module, EasyStatePattern, provides decorators which are used to indicate methods which are to be implemented in the states and which methods cause state transitions. The decorators also provide the code that calls the corresponding method in the current state. Not having an abstract state class, the states derive from the context class instead. A state transition table for the context is set up following the definition of the states.
"""
Uses the EasyStatePattern module at http://code.activestate.com/recipes/576613/.
"""
import EasyStatePattern as esp
# The context class (also serving as the Abstract State Class in GOF discussion)
class Parent(object):
moodTable = esp.StateTable('mood')
def __init__(self, pocketbookCash, piggybankCash):
Parent.moodTable.initialize( self)
self.pocketBook = pocketbookCash
self.piggyBank = piggybankCash
@esp.Transition(moodTable)
def getPromotion(self): pass
@esp.Transition(moodTable)
def severalDaysPass(self): pass
@esp.Event(moodTable)
def askForMoney(self, amount): pass
@esp.TransitionEvent(moodTable)
def cashPaycheck(self, amount): pass
@esp.TransitionEvent(moodTable)
def getUnexpectedBill(self, billAmount ): pass
def onEnter(self):
print 'Mood is now', self.mood.name()
#
# Below are defined the states. Each state is a class, States may be derived from
# other states. Topmost states must have a __metaclass__ = stateclass( state_machine_class )
# declaration.
#
class Normal( Parent ):
__metaclass__ = esp.stateclass( Parent )
def askForMoney(self, amount):
amountToReturn = min(amount, self.pocketBook )
self.pocketBook -= amountToReturn
if amountToReturn == 0.0:
self.mood.nextState = Broke
return amountToReturn
def cashPaycheck(self, amount):
self.pocketBook += .7 * amount
self.piggyBank += .3*amount
def getUnexpectedBill(self, billAmount ):
amtFromPktBook = min(billAmount, self.pocketBook)
rmngAmt = billAmount - amtFromPktBook
self.piggyBank -= rmngAmt
self.pocketBook -= amtFromPktBook
class Happy( Parent ):
__metaclass__ = esp.stateclass( Parent )
def askForMoney(self, amount):
availableMoney = self.pocketBook + self.piggyBank
amountToReturn = max(min(amount, availableMoney), 0.0)
amountFromPktbook = min(amountToReturn, self.pocketBook)
self.pocketBook -= amountFromPktbook
self.piggyBank -= (amountToReturn - amountFromPktbook)
if amountToReturn == 0.0:
self.mood.nextState = Broke
return amountToReturn
def cashPaycheck(self, amount):
self.pocketBook += .75 * amount
self.piggyBank += .25*amount
def getUnexpectedBill(self, billAmount ):
amtFromPktBook = min(billAmount, self.pocketBook)
rmngAmt = billAmount - amtFromPktBook
self.piggyBank -= rmngAmt
self.pocketBook -= amtFromPktBook
def onEnter(self):
print 'Yippee! Woo Hoo!', self.mood.name()*3
class Grouchy( Parent ):
__metaclass__ = esp.stateclass( Parent )
def askForMoney(self, amount):
return 0.0
def cashPaycheck(self, amount):
self.pocketBook += .70 * amount
self.piggyBank += .30*amount
def getUnexpectedBill(self, billAmount ):
amtFromPktBook = min(billAmount, self.pocketBook)
rmngAmt = billAmount - amtFromPktBook
self.piggyBank -= rmngAmt
self.pocketBook -= amtFromPktBook
class Broke( Normal ):
""" No metaclass declaration as its as subclass of Normal. """
def cashPaycheck(self, amount):
piggyBankAmt = min ( amount, max(-self.piggyBank, 0.0))
rmngAmount = amount - piggyBankAmount
self.pocketBook += .40 * rmngAmount
self.piggyBank += (.60 * rmngAmount + piggyBankAmt)
def askForMoney(self, amount):
amountToReturn = min(amount, self.pocketBook )
self.pocketBook -= amountToReturn
if amountToReturn == 0.0:
self.mood.nextState = Broke
return amountToReturn
def onLeave(self):
print 'Glad to finally have those bills paid.'
#Setup the Transition table in the context class
# (getPromotion, severalDaysPass, cashPaycheck, getUnexpectedBill )
Parent.moodTable.nextStates( Normal, ( Happy, Normal, Normal, Grouchy ))
Parent.moodTable.nextStates( Happy, ( Happy, Happy, Happy, Grouchy ))
Parent.moodTable.nextStates( Grouchy, ( Happy, Normal, Grouchy, Grouchy ))
Parent.moodTable.nextStates( Broke, ( Normal, Broke, Grouchy, Broke ))
# This specifies the initial state.
Parent.moodTable.initialstate = Normal
def Test():
dad = Parent(50.0, 60.0)
mom = Parent( 60.0, 100.0)
amount = 30.0
print 'from Dad', amount, dad.askForMoney(amount) # > 30.0 30.0
print 'from Dad', amount, dad.askForMoney(amount) # > 30.0 20.0
dad.cashPaycheck( 40.0)
print 'from Dad', amount, dad.askForMoney(amount) # > 30.0 28.0
dad.cashPaycheck( 80.0)
dad.getUnexpectedBill(50.0 ) # Grouchy
print 'from Dad', amount, dad.askForMoney(amount) # > 30.0 0.0
dad.severalDaysPass() # Normal
print 'from Dad', amount, dad.askForMoney(amount) # > 30.0 6.0
dad.severalDaysPass()
print 'from Dad', amount, dad.askForMoney(amount) # > 30.0 0.0
dad.cashPaycheck( 50.0)
print 'from Dad', amount, dad.askForMoney(amount) # > 30.0 30.0
dad.getPromotion() # Yippee!
dad.cashPaycheck( 100.0)
amount = 200.0
print 'from Dad', amount, dad.askForMoney(amount) # > 200.0 200.0
print 'from Mom', amount, mom.askForMoney(amount) # > 200.0 60.0
Test()
See also
External links
- Jt J2EE Pattern Oriented Framework
- State pattern in UML and in LePUS3 (a formal modelling language)
- State Pattern using Java : A Different Approach
References
- ^ Gamma, Erich (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. p. 395. ISBN 0201633612.
{{cite book}}
: Unknown parameter|coauthors=
ignored (|author=
suggested) (help)