State pattern

From Wikipedia, the free encyclopedia

Jump to: navigation, search
State in UML
State in LePUS3 (legend)

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].

Contents

[edit] 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.

As each state represents a tool, we call their shared class 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

And the cursor itself would look like this:

 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.


[edit] Example in Python

"""
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()

[edit] See also

[edit] External links

[edit] References

  1. ^ Gamma, Erich; Richard Helm, Ralph Johnson, John M. Vlissides (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. pp. 395. ISBN 0201633612. 
Personal tools