Chapter 29. The State Pattern

The State pattern is used when you want to have an object represent the state of your application and switch application states by switching objects. For example, you could have an enclosing class switch between a number of related contained classes and pass method calls on to the current contained class. Design Patterns suggests that the State pattern switches between internal classes in such a way that the enclosing object appears to change its class. In VB, at least, this is a bit of an exaggeration, but the actual purpose to which the classes are applied can change significantly.

Many programmers have had the experience of creating a class that performs slightly different computations or displays different information based on the arguments passed into the class. This frequently leads to some types of select case or if-else statements inside the class that determine which behavior to carry out. It is this inelegance that the State pattern seeks to replace.

Sample Code

Let's consider the case of a drawing program similar to the one we developed for the Memento class. Our program will have toolbar buttons for Select, Rectangle, Fill, Circle, and Clear. We show this program in Figure 29-1.

A simple drawing program we will use for illustrating the State pattern

Figure 29-1. A simple drawing program we will use for illustrating the State pattern

Each one of the tool buttons does something rather different when it is selected and you click or drag your mouse across the screen. Thus, the state of the graphical editor affects the behavior the program should exhibit. This suggests some sort of design using the State pattern.

Initially we might design our program like this, with a Mediator managing the actions of five command buttons, as shown in Figure 29-2. However, this initial design puts the entire burden of maintaining the state of the program on the Mediator, and we know that the main purpose of a Mediator is to coordinate activities between various controls, such as the buttons. Keeping the state of the buttons and the desired mouse activity inside the Mediator can make it unduly complicated, as well as leading to a set of If or Select tests that make the program difficult to read and maintain.

One possible interaction between the classes needed to support the simple drawing program

Figure 29-2. One possible interaction between the classes needed to support the simple drawing program

Further, this set of large, monolithic conditional statements might have to be repeated for each action the Mediator interprets, such as mouseUp, mouseDrag, rightClick, and so forth. This makes the program very hard to read and maintain.

Instead, let's analyze the expected behavior for each of the buttons.

  1. If the Select button is selected, clicking inside a drawing element should cause it to be highlighted or appear with “handles.” If the mouse is dragged and a drawing element is already selected, the element should move on the screen.

  2. If the Rect button is selected, clicking on the screen should cause a new rectangle drawing element to be created.

  3. If the Fill button is selected and a drawing element is already selected, that element should be filled with the current color. If no drawing is selected, then clicking inside a drawing should fill it with the current color.

  4. If the Circle button is selected, clicking on the screen should cause a new circle drawing element to be created.

  5. If the Clear button is selected, all the drawing elements are removed.

There are some common threads among several of these actions we should explore. Four of them use the mouse click event to cause actions. One uses the mouse drag event to cause an action. Thus, we really want to create a system that can help us redirect these events based on which button is currently selected.

Let's consider creating a State object that handles mouse activities.

'Interface State
Public Sub mouseDown(X As Integer, Y As Integer)
End Sub
'-----
Public Sub mouseUp(X As Integer, Y As Integer)
End Sub
'-----
Public Sub mouseDrag(X As Integer, Y As Integer)
End Sub

We'll include the mouseUp event in case we need it later. Then we'll create four derived State classes for Pick, Rect, Circle, and Fill and put instances of all of them inside a StateManager class that sets the current state and executes methods on that state object. In Design Patterns, this StateManager class is referred to as a Context. This object is illustrated in Figure 29-3.

A StateManager class that keeps track of the current state

Figure 29-3. A StateManager class that keeps track of the current state

A typical State object simply overrides (in VB6, implements and fills out) those event methods that it must handle specially. For example, this is the complete Rectangle state object.

'Class RectState
Implements State
Private med As Mediator
Public Sub init(md As Mediator)
 Set med = md
End Sub
'-----
Private Sub State_mouseDown(X As Integer, Y As Integer)
Dim vr As New VisRectangle
 vr.init X, Y
 med.addDrawing vr
End Sub
'-----
Private Sub State_mouseDrag(X As Integer, Y As Integer)
End Sub
'-----
Private Sub State_mouseUp(X As Integer, Y As Integer)
End Sub

The RectState object simply tells the Mediator to add a rectangle drawing to the drawing list. Similarly, the Circle state object tells the Mediator to add a circle to the drawing list.

'Class CircleState
Implements State
Private med As Mediator
'-----
Public Sub init(md As Mediator)
 Set med = md
End Sub
'-----
Private Sub State_mouseDown(X As Integer, Y As Integer)
  Dim c As visCircle
  Set c = New visCircle
  c.init X, Y
  med.addDrawing c
End Sub
'-----
Private Sub State_mouseDrag(X As Integer, Y As Integer)
End Sub
'-----
Private Sub State_mouseUp(X As Integer, Y As Integer)
End Sub

The only tricky button is the Fill button because we have defined two actions for it.

  1. If an object is already selected, fill it.

  2. If the mouse is clicked inside an object, fill that one.

In order to carry out these tasks, we need to add the selectOne method to our base State interface. This method is called when each tool button is selected.

'Interface State
Public Sub mouseDown(X As Integer, Y As Integer)
End Sub
'-----
Public Sub mouseUp(X As Integer, Y As Integer)
End Sub
'-----
Public Sub mouseDrag(X As Integer, Y As Integer)
End Sub
'-----
Public Sub selectOne(d As Drawing)
End Sub

The Drawing argument is either the currently selected Drawing or null if none is selected. In this simple program, we have arbitrarily set the fill color to red, so our Fill state class becomes the following.

'Class FillState
Implements State
Private med As Mediator
Private color As ColorConstants
'-----
Public Sub init(md As Mediator)
 Set med = md
 color = vbRed
End Sub
'-----
Private Sub State_mouseDown(X As Integer, Y As Integer)
  Dim drawings As Collection
  Dim i As Integer
  Dim d As Drawing
    'Fill drawing if you click inside one
     Set drawings = med.getDrawings()
     For i = 1 To drawings.Count
        Set d = drawings(i)
        If d.contains(X, Y) Then
           d.setFill color 'fill drawing
         End If
     Next i
End Sub
'-----
Private Sub State_mouseDrag(X As Integer, Y As Integer)
End Sub
'-----
Private Sub State_mouseUp(X As Integer, Y As Integer)
End Sub
'-----
Private Sub State_selectOne(d As Drawing)
'Fill drawing if selected
   d.setFill color 'fill that drawing
End Sub

Switching between States

Now that we have defined how each state behaves when mouse events are sent to it, we need to examine how the StateManager switches between states. We create an instance of each state, and then we simply set the currentState variable to the state indicated by the button that is selected.

'Class StateManager
Private currentState As State
Private rState As RectState
Private aState As ArrowState
Private cState As CircleState
Private fState As FillState
'-----
Public Sub init(med As Mediator)
'create an instance of each state
  Set rState = New RectState
  Set cState = New CircleState
  Set aState = New ArrowState
  Set fState = New FillState
    'and initialize them
  rState.init med
  cState.init med
  aState.init med
  fState.init med
  'set default state
  Set currentState = aState
End Sub

Note that in this version of the StateManager, we create an instance of each state during the constructor and copy the correct one into the state variable when the set methods are called. It would also be possible to create these states on demand. This might be advisable if there are a large number of states that each consume a fair number of resources.

The remainder of the state manager code simply calls the methods of whichever state object is current. This is the critical piece—there is no conditional testing. Instead, the correct state is already in place, and its methods are ready to be called.

Public Sub mouseDown(X As Integer, Y As Integer)
 currentState.mouseDown X, Y
End Sub
'-----
Public Sub mouseUp(X As Integer, Y As Integer)
 currentState.mouseUp X, Y
End Sub
'-----
Public Sub mouseDrag(X As Integer, Y As Integer)
 currentState.mouseDrag X, Y
End Sub
'-----
Public Sub selectOne(d As Drawing, c As ColorConstants)
  currentState.selectOne d
End Sub

How the Mediator Interacts with the State Manager

We mentioned that it is clearer to separate the state management from the Mediator's button and mouse event management. The Mediator is the critical class, however, since it tells the StateManager when the current program state changes. The beginning part of the Mediator illustrates how this state change takes place. Note that each button click calls one of these methods and changes the state of the application. The remaining statements in each method simply turn off the other toggle buttons so only one button at a time can be depressed.

'Class Mediator
    Private startRect As Boolean
    Private selectedIndex As Integer
    Private rectb As RectButton
    Private dSelected As Boolean
    Private drawings As Collection
    Private undoList As Collection
    Private rbutton As RectButton
    Private filbutton As FillButton
    Private circButton As CircleButton
    Private arrowButton As PickButton
    Private canvas As PictureBox
    Private selectedDrawing As Integer
    Private stmgr As StateManager
'-----
 Public Sub init(Pic As PictureBox)
   startRect = False
   dSelected = False
   Set drawings = New Collection
   Set undoList = New Collection
   Set stmgr = New StateManager
   stmgr.init Me
   Set canvas = Pic
End Sub
'-----
Public Sub startRectangle()
   stmgr.setRect
   arrowButton.setSelected (False)
   circButton.setSelected (False)
   filbutton.setSelected (False)
End Sub
'-----
Public Sub startCircle()
   Dim st As State
   stmgr.setCircle
   rectb.setSelected False
   arrowButton.setSelected False
   filbutton.setSelected False
End Sub

As we did in the discussion of the Memento pattern, we create a series of button Command objects paralleling the toolbar buttons and keep them in an array to be called when the toolbar button click event occurs.

Private Sub Form_Load()
 Set buttons = New Collection
 'create an instance of the Mediator
 Set med = New Mediator
 med.init Pic
 'Create the button command objects
 'give each of them access to the Mediator
 Set pickb = New PickButton
 pickb.init med, tbar.buttons(1)
 Set rectb = New RectButton
 rectb.init med, tbar.buttons(2)
 Set filb = New FillButton
 filb.init med, tbar.buttons(3)
 Set cb = New CircleButton
 cb.init med, tbar.buttons(4)
 Set clrb = New ClearButton
 clrb.init med

 Set undob = New UndoButton
 undob.init med
'keep a Collection of the button Command objects
 buttons.Add pickb
 buttons.Add rectb
 buttons.Add filb
 buttons.Add cb
 buttons.Add undob
 buttons.Add clrb
End Sub

These Execute methods in turn call the preceding startXxx methods.

Private Sub tbar_ButtonClick(ByVal Button As MSComctlLib.Button)
 Dim i As Integer
 Dim cmd As Command
 'find out which button was clicked
 i = Button.index
 'get that command object
 Set cmd = buttons(i)
 cmd.Execute    'and execute it
End Sub

The class diagram for this program illustrating the State pattern in this application is illustrated in two parts. The State section is shown in Figure 29-4.

The StateManager and the Mediator

Figure 29-4. The StateManager and the Mediator

The connection of the Mediator to the buttons is shown in Figure 29-5.

Interaction between the buttons and the Mediator

Figure 29-5. Interaction between the buttons and the Mediator

Handling the Fill State

The Fill State object is only slightly more complex because we have to handle two cases. The program will fill the currently selected object if one exists or fill the next one that you click on. This means there are two State methods we have to fill in for these two cases, as we see here.

'Class FillState
Implements State
Private med As Mediator
'-----
Public Sub init(md As Mediator)
 Set med = md
End Sub
'-----
Private Sub State_mouseDown(x As Integer, y As Integer)
  Dim drawings As Collection
  Dim i As Integer
  Dim d As Drawing
    'Fill drawing if you click inside one
    i = med.findDrawing(x, y)
    If i > 0 Then
      Set d = med.getDrawing(i)
      d.setFill True 'fill drawing
    End If
End Sub
'-----
Private Sub State_mouseDrag(x As Integer, y As Integer)
End Sub
'-----
Private Sub State_mouseUp(x As Integer, y As Integer)
End Sub
'-----
Private Sub State_selectOne(d As Drawing)
'Fill drawing if selected
   d.setFill True 'fill that drawing
End Sub

Handling the Undo List

Now we should be able to undo each of the actions we carry out in this drawing program, and this means that we keep them in an undo list of some kind. These are the actions we can carry out and undo.

  1. Creating a rectangle

  2. Creating a circle

  3. Moving a rectangle or circle

  4. Filling a rectangle or circle

In our discussion of the Memento pattern, we indicated that we would use a Memento object to store the state of the rectangle object and restore its position from that Memento as needed. This is generally true for both rectangles and circles, since we need to save and restore the same kind of position information. However, the addition of rectangles or circles and the filling of various figures are also activities we want to be able to undo. And, as we indicated in the previous Memento discussion, the idea of checking for the type of object in the undo list and performing the correct undo operation is a really terrible idea.

'really terrible programming approach
  Set obj = undoList(undoList.count)
  undoList.remove undoList.count   'and remove it
  If Not (TypeOf obj Is Memento) Then
       drawings.remove drawings.count
  Else
       obj.restore
End If

Instead, let's define the Memento as an interface.

'Interface Memento
Public Sub init(d As Drawing)
End Sub
'-----
Public Sub restore()
'restore the state of an object
End Sub

Then all of the objects we add into the undo list will implement the Memento interface and will have a restore method that performs some operation. Some kinds of Mementos will save and restore the coordinates of drawings, and others will simply remove drawings or undo fill states.

First, we will have both our circle and rectangle objects implement the Drawing interface.

'Interface Drawing
Public Sub setSelected(b As Boolean)
End Sub
'-----
Public Sub draw(g As PictureBox)
End Sub
'-----
Public Sub move(xpt As Integer, ypt As Integer)
End Sub
'-----
Public Function contains(X As Integer, Y As Integer) As Boolean
End Function
'-----
Public Sub setFill(b as Boolean)
End Sub
'-----
'Property methods used to save and restore state
Property Get rects() As Rectangle
End Property
'-----
Property Set rects(rc As Rectangle)
End Property

The Memento we will use for saving the state of a Drawing will be similar to the one we used in the Memento chapter, except that we specifically make it implement the Memento interface.

'Class DrawMemento
Implements Memento
Private X As Integer, Y As Integer
Private w As Integer, h As Integer
Private rect As Rectangle
Private visDraw As Drawing
'-----
Private Sub Memento_init(d As Drawing)
'save the state of a visual rectangle
 Set visDraw = d
 Set rect = visDraw.rects
 X = rect.X
 Y = rect.Y
 w = rect.w
 h = rect.h
End Sub
'-----
Private Sub Memento_restore()
'restore the state of a drawing object
 rect.X = X
 rect.Y = Y
 rect.h = h
 rect.w = w
 Set visDraw.rects = rect
End Sub

Now for the case where we just want to remove a drawing from the list to be redrawn, we create a class to remember that index of that drawing and remove it when its restore method is called.

'Class DrawInstance
Implements Memento
'treats a drawing index as an object
Private intg As Integer
Private med As Mediator
Public Sub init(a As Integer, md As Mediator)
 intg = a       'remember the index
 Set med = md
End Sub
Property Get integ() As Integer
 integ = intg
End Property
'-----
Private Sub Memento_init(d As Drawing)
End Sub
'-----
Private Sub Memento_restore()
 'remove that drawing from the list
 med.removeDrawing intg
End Sub

We handle the FillMemento in just the same way, except that its restore method turns off the fill flag for that drawing element.

'Class FillMemento
Implements Memento
Private index As Integer
Private med As Mediator
'-----
Public Sub init(a As Integer, md As Mediator)
 index = a
 Set med = md
End Sub
'-----
Private Sub Memento_init(d As Drawing)
End Sub
'-----
Private Sub Memento_restore()
Dim d As Drawing
 Set d = med.getDrawing(index)
 d.setFill False
End Sub

Filling Circles in VB6

VB6 does not have a way to draw filled circles that is analogous to the way we draw filled rectangles. Instead, circles are filled if the Picturebox control's FillStyle is set appropriately. However, in that case, it fills all circles you draw, whether you want to or not. Therefore, for VB6, we approximate filling the circles by drawing concentric circles inside the original circle and then drawing an inscribed filled rectangle as well.

If filled Then
  For i = r To 1 Step -1
    Pic.Circle (xc, yc), i, fillColor
  Next i
  Pic.Line (x + 4, y + 4)-(x + w - 6, y + w - 6), fillColor, BF
End If

A State Pattern in VB.NET

The State pattern in VB7 is similar to that in VB6. We use the same interfaces for the Memento and Drawing classes.

Public Interface Memento
    Sub restore()
End Interface

Public Interface Drawing
    Sub setSelected(ByVal b As Boolean)
    Sub draw(ByVal g As Graphics)
    Sub move(ByVal xpt As Integer, ByVal ypt As Integer)
    Function contains(ByVal x As Integer, _
             ByVal y As Integer) As Boolean
    Sub setFill(ByVal b As Boolean)
    Property rects() As vbpatterns.Rectangle
End Interface

However, there is some advantage in creating a State class with empty methods and overriding only those that a particular derived State class will require. So our base State class is as follows.

Public Class State
    Public Overridable Sub mouseDown(ByVal x As Integer,_
                                    ByVal y As Integer)
    End Sub
    '-----
    Public Overridable Sub mouseUp(ByVal x As Integer, _
                                  ByVal y As Integer)
    End Sub
    '-----
    Public Overridable Sub mouseDrag(ByVal x As Integer, _
                                     ByVal y As Integer)
    End Sub
    '-----
    Public Overridable Sub selectOne(ByVal d As Drawing)
    End Sub
End Class

Then our derived state classes need only override the methods important to them. For example, the RectState class only responds to MouseDown.

Public Class RectState
    Inherits State
    Private med As Mediator
    Public Sub New(ByVal md As Mediator)
        med = md
    End Sub
    '-----
    Public Overrides Sub mouseDown(ByVal x As Integer, _
        ByVal y As Integer)
        Dim vr As New VisRectangle(x, y)
        med.addDrawing(vr)
    End Sub
End Class

We can take some useful advantage of inheritance in designing our visRectangle and visCircle classes. We make visRectangle implement the Drawing interface and then have visCircle inherit from visRectangle. This allows us to reuse the setSelected, setFill, and move methods and the rects properties. In addition, we can split off the drawHandle method and use it in both classes. The revised visRectangle class looks like this.

Public Class VisRectangle
    Implements Drawing
    Protected x, y, w, h As Integer
    Private rect As vbpatterns.Rectangle
    Protected selected As Boolean
    Protected filled As Boolean
    Protected bBrush As SolidBrush
    Protected rBrush As SolidBrush
    Protected bPen As Pen
    Private fillColor As Color
    '-----
    Public Sub New(ByVal xp As Integer, _
                  ByVal yp As Integer)
        x = xp     'save coordinates
        y = yp
        w = 40     'default size
        h = 30
        fillColor = Color.Red
        bbrush = New SolidBrush(color.Black)
        rbrush = New SolidBrush(fillcolor)
        bPen = New Pen(color.Black)
        saveAsRect() 'keep in rectangle class as well
    End Sub
    '-----
    Protected Sub saveAsRect()
        rect = New vbpatterns.Rectangle(x, y, w, h)
    End Sub
    '-----
    Public Function contains(ByVal xp As Integer, _
        ByVal yp As Integer) As Boolean _
             Implements Drawing.contains
        Return rect.contains(xp, yp)
    End Function
    '-----
    Public Overridable Sub draw(ByVal g As Graphics) _
        Implements Drawing.draw
        'draw rectangle
        If filled Then
            g.FillRectangle(rbrush, x, y, w, h)
        End If
        g.DrawRectangle(bpen, x, y, w, h)
        If selected Then   'draw handles
            drawHandles(g)
        End If
    End Sub
    '-----
   Protected Sub drawHandles(ByVal g As Graphics)
    'Draws handles on sides of square or circle
    g.fillrectangle(bBrush, (x + w  2), (y - 2), 4, 4)
    g.FillRectangle(bbrush, x - 2, y + h  2, 4, 4)
    g.FillRectangle(bbrush, x + (w  2), y + h - 2, 4, 4)
    g.FillRectangle(bbrush, x + (w - 2), y + (h  2), 4, 4)
   End Sub
    '-----
    Public Overridable Sub move(ByVal xpt As Integer, _
         ByVal ypt As System.Integer) _
            Implements VBNetState.Drawing.move
    'Moves drawing to new coordinates
        x = xpt
        y = ypt
        saveAsRect()
    End Sub
    '-----
    Friend Property rects() As vbPatterns.Rectangle _
        Implements Drawing.rects
     'Allows changing of remembered state
        Set
            x = value.x
            y = value.y
            w = value.w
            h = value.h
            saveAsRect()
        End Set
        Get
            Return rect
        End Get
    End Property
    '-----
    Public Sub setFill(ByVal b As Boolean) _
        Implements Drawing.setFill
        filled = b
    End Sub
    '-----
    Public Sub setSelected(ByVal b As Boolean) _
        Implements VBNetState.Drawing.setSelected
        selected = b
    End Sub
End Class

However, our visCircle class only needs to override the draw method and have a slightly different constructor.

Public Class VisCircle
    Inherits VisRectangle
    Private r As Integer
    '-----
    Public Sub New(ByVal xp As Integer, _
            ByVal yp As Integer)
        MyBase.New(xp, yp)
        r = 15
        w = 30
        h = 30
        saveAsRect()
    End Sub
    '-----
    Public Overrides Sub draw(ByVal g As Graphics)
        'Fill the circle if flag set
        If filled Then
            g.FillEllipse(rbrush, x, y, w, h)
        End If
        g.DrawEllipse(bpen, x, y, w, h)
        If selected Then
            drawHandles(g)
        End If
    End Sub
End Class

Note that since we have made the x, y, and filled variables Protected, we can refer to them in the derived visCircle class without declaring them at all. Note that there is a valid fill method in VB7 to fill circles (ellipses).

The Mediator, Memento, and StateManager classes are essentially identical to those we wrote for VB6. However, we can simplify the overall program a great deal by creating derived classes from the ToolBarButton class and making them implement the Command interface as well.

'A toolbar button class that also
'has a command interface
Public Class CmdToolbarButton
    Inherits System.WinForms.ToolBarButton
    Implements Command
    Protected med As Mediator
    Protected selected As Boolean
    Public Sub New(ByVal caption As String, _
                  ByVal md As mediator)
        MyBase.New()
        Me.Text = caption
        med = md
        InitializeComponent()
    End Sub
    '-----
    Public Overridable Sub setSelected(ByRef b As Boolean)
        selected = b
    End Sub
    '-----
    Public Overridable Sub Execute() _
        Implements Command.Execute
    End Sub
End Class

We then can derive our RectButton, CircleButton, ClearButton, Undo Button, FillButton, and PickButton classes from the CmdToolBarButton class and give each of them the appropriate Execute method. The RectButton class is just that straightforward.

Public Class RectButton
    Inherits CmdToolbarButton
    '-----
    Public Sub New(ByVal md As Mediator)
        MyBase.New("Rectangle", md)
        Me.Style = ToolBarButtonStyle.ToggleButton
        med.registerRectButton(Me)
    End Sub
    '-----
    Public Overrides Sub Execute()
        med.startrectangle()
    End Sub
End Class

The only disadvantage to this approach is that you have to add the buttons to the toolbar programmatically instead of using the designer. However, this just amounts to adding the buttons to a collection. We create the empty toolbar in the designer, giving it the name Tbar, and then add the buttons to it.

Private Sub init()
        'called from New constructir
        'create a Mediator
        med = New Mediator(Pic)
        'create the buttons
        RctButton = New RectButton(med)
        ArowButton = New PickButton(med)
        CircButton = New CircleButton(med)
        flbutton = New FillButton(med)
        undoB = New UndoButton(med)
        clrb = New ClearButton(med)
        'add the buttons into the toolbar
        TBar.Buttons.Add(ArowButton)
        TBar.Buttons.Add(RctButton)
        TBar.Buttons.Add(CircButton)
        TBar.Buttons.Add(flbutton)
        'include a separator
        Dim sep As New ToolBarButton()
        sep.Style = ToolBarButtonStyle.Separator
        TBar.Buttons.Add(sep)
        TBar.Buttons.Add(undoB)
        TBar.Buttons.Add(clrb)
    End Sub

This makes the processing of the button clicks completely object oriented because we do not have to know which button was clicked. They are all Command objects, and we just call their execute methods.

'process button commands
    Private Sub TBar_ButtonClick( _
            ByVal sender As System.Object, _
            ByVal e As ToolBarButtonClickEventArgs) _
            Handles TBar.ButtonClick
        Dim cmd As Command
        Dim tbutn As ToolBarButton = e.Button
        cmd = CType(tbutn, Command) 'get the command object
        cmd.Execute()           'and execute it
    End Sub

Mediators and the God Class

One real problem with programs with this many objects interacting is putting too much knowledge of the system into the Mediator so it becomes a “god class.” In the preceding example, the Mediator communicates with the six buttons, the drawing list, and the StateManager. We could write this program another way so that the button Command objects communicate with the StateManager and the Mediator only deals with the buttons and the drawing list. Here, each button creates an instance of the required state and sends it to the StateManager. This we will leave as an exercise for the reader.

Consequences of the State Pattern

  1. The State pattern creates a subclass of a basic State object for each state an application can have and switches between them as the application changes between states.

  2. You don't need to have a long set of conditional if or switch statements associated with the various states, since each is encapsulated in a class.

  3. Since there is no variable anywhere that specifies which state a program is in, this approach reduces errors caused by programmers forgetting to test this state variable

  4. You could share state objects between several parts of an application, such as separate windows, as long as none of the state objects have specific instance variables. In this example, only the FillState class has an instance variable, and this could be easily rewritten to be an argument passed in each time.

  5. This approach generates a number of small class objects but in the process simplifies and clarifies the program.

  6. In VB, all of the States must implement a common interface, and they must thus all have common methods, although some of those methods can be empty. In other languages, the states can be implemented by function pointers with much less type checking and, of course, greater chance of error.

State Transitions

The transition between states can be specified internally or externally. In our example, the Mediator tells the StateManager when to switch between states. However, it is also possible that each state can decide automatically what each successor state will be. For example, when a rectangle or circle drawing object is created, the program could automatically switch back to the Arrow-object State.

Thought Questions

Thought Questions
  1. Rewrite the StateManager to use a Factory pattern to produce the states on demand.

  2. While visual graphics programs provide obvious examples of State patterns, server programs can benefit by this approach. Outline a simple server that uses a state pattern.

Programs on the CD-ROM

State VB6 state drawing program
StateVbnet VB7 state drawing program
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset