Chapter 12. State Pattern

<feature><title>In This Chapter</title> </feature>

The State pattern is a valuable pattern in ActionScript development. It allows an object to change its behavior when its internal state changes. Take a toggle button as an example. The toggle button must maintain two separate states: one for selected and one for unselected. The two states share the same structure, but they have very different functionality. In addition to the visual display being different, they most likely handle a click event differently, too. This is where the State pattern becomes valuable. The State pattern defines a standard methodology for handling encapsulated states.

There are many ways to implement the State pattern, but they all come to the same result: The object’s class appears to change. Obviously, we aren’t talking about changing the object’s type at runtime, but we are talking about changing nearly every operation in a class based on its internal state.

The best way to demonstrate the State pattern is with an example. Therefore, we’re going to build a basketball shooter. Our example will build a shooter object with three different states: lay up, free throw, and three-pointer. Each of these states has characteristics such as accuracy and point value. We’ll use this shooter object in the context of a basketball game. When we tell the shooter to shoot the ball, we can calculate whether the shot was made and for how many points based on the internal state of the shooter object.

Simple State Example

Our first attempt at representing the basketball shooter and its various states is very simple. In fact, you’ve probably built classes just like this before. This shooter class meets all the criteria of the State pattern but is a naive and inelegant solution that presents further problems. We’ll look at this example first to understand how we can later improve on this.

Create the Simple Shooter Class

The SimpleShooter class holds all the shooter functionality for each state. The state is set in the setState() method. This method takes a stateName as the parameter. The value of this parameter is saved in a class property called stateName.

There are three constants defined in this class that represent the three state names: LAY_UP_STATE, FREE_THROW_STATE, and THREE_POINTER_STATE. They should be used when calling the setState() method to ensure accuracy.

The SimpleShooter class also has a getAccuracy() method to determine the percentage of shots made from that state. And there is a getPointValue() method that returns the point value of a made shot from that state. Each of these methods has a switch statement that determines the state in which the object is.

package com.peachpit.aas3wdp.stateexample {

   public class SimpleShooter {

      private var stateName:String;
      public static const LAY_UP_STATE:String = "lay_up_state";
      public static const FREE_THROW_STATE:String = "free_throw_state";
      public static const THREE_POINTER_STATE:String = "three_pointer_state";

      public function SimpleShooter() {}

      // Returns the shot accuracy percent of the current state
      public function getAccuracy():Number {
         switch(stateName) {
           case LAY_UP_STATE:
              return 0.9;
           case FREE_THROW_STATE:
              return 0.7;
           case THREE_POINTER_STATE:
              return 0.4;
           default:
              return 0;
         }
      }

      // Returns the made shot point value of the current state
      public function getPointValue():Number {
         switch(stateName) {
           case LAY_UP_STATE:
              return 2;
           case FREE_THROW_STATE:
              return 1;
           case THREE_POINTER_STATE:
              return 3;
           default:
              return 0;
         }
      }

      // Sets the current state of the object
         public function setState(stateName:String):void {
            this.stateName = stateName;
         }
      }
}

Create the Main Example Class

We’ll build a class called SimpleShooterExample, which serves as the starting point for this example. This class creates the shooter object, sets the state, initiates ten shots at a constant time interval, calculates the outcome (whether or not the ball went through the hoop) based on the accuracy property, and keeps track of the points. After each shot, we output the outcome of the shot. The outcome of the shot and the point value are determined by the internal state of the shooter object. The following is the code for our implementation class:

package {

   import flash.display.Sprite;
   import flash.utils.Timer;
   import flash.events.TimerEvent;
   import com.peachpit.aas3wdp.stateexample.SimpleShooter;

   public class SimpleShooterExample extends Sprite {

     private var _points:Number;
     private var _shooter:SimpleShooter;

     public function SimpleShooterExample() {
        // Initially set the points to zero
        _points = 0;
        // Create the SimpleShooter instance and set its state
        _shooter = new SimpleShooter();
        _shooter.setState(SimpleShooter.THREE_POINTER_STATE);

        var shotInterval:Timer = new Timer(1000, 10);
        shotInterval.addEventListener(TimerEvent.TIMER, onShot);
        shotInterval.start();
     }

     private function calculateShot(accuracy:Number):Boolean {
        return Math.random() < accuracy;
     }

     private function onShot(event:TimerEvent):void {
        if(calculateShot(_shooter.getAccuracy())) {
           _points += _shooter.getPointValue();
           trace("Made the Shot!  " + _points + " point(s)");
        }else {
           trace("Missed the Shot!")
        }
     }
  }
}

The SimpleShooterExample class has two properties: points and shooter. The points property holds the running total of our points across all ten shots; the shooter property holds the instance of the SimpleShooter object. Inside the constructor, we create that SimpleShooter object and set the state of it.

The shot is calculated by grabbing the accuracy of the current shot. This is a percentage at which shots from that state are typically made. We run that though a simple probability function called calculateShot(). If the accuracy is 80%, the calculateShot() method returns true 80% of the times it is called and false for the remaining 20%.

Problems with This Example

Although the example we just described works and is simple to understand, there are a couple major problems with it.

The first problem is scalability. This solution simply will not scale well. Consider an option with 100 states and 20 methods per state. The resulting class would be huge. And each time you add or remove a state, you’d have to modify every method.

This transitions us nicely to our second problem: maintainability. If we have to modify massive amounts of code for each change request, we’re opening ourselves up to having a lot of bugs. To prevent this from happening, we should encapsulate each of the states in its own class and close those states for modification. This makes the application easier to perform Quality Assurance (QA) testing and debugging.

Encapsulating the States

Let’s try this again. This time we’re going to encapsulate each state into its own class so that the code is easier to maintain and scales well. Figure 12.1 shows what our solution looks like.

Encapsulating the states.

Figure 12.1. Encapsulating the states.

The Shooter State Interface

First, we create an interface that all of our state classes implement. All our state objects implement the IShooterState interface, therefore, we can treat them interchangeably. This is known as polymorphism. The IShooterState interface has two methods: getAccuracy() and getPointValue(). Notice that we typed the state property in the ShooterStateMachine class to IShooterState so that we can store an instance of any object that implements that interface.

package com.peachpit.aas3wdp.stateexample {

   public interface IShooterState {

      function getAccuracy():Number;

      function getPointValue():Number;

   }
}

State Objects

All our state objects are basically the same. They encapsulate the state-specific information for our application. For example, the LayUpState defines an accuracy of 90% and a point value of 2. By encapsulating all the state specific logic into objects, we make them easier to manage and more flexible. They’re easier to manage because modifications to one state don’t affect the other states. It’s also easier to add new states.

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.statepattern.IShooterState;

   internal class LayUpState implements IShooterState {

     public function getAccuracy():Number {
        return 0.9;
     }

     public function getPointValue():Number {
        return 2;
     }

   }

}

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.statepattern.IShooterState;

   internal class FreeThrowState implements IShooterState {

      public function getAccuracy():Number {
         return 0.7;
 
      }

      public function getPointValue():Number {
         return 1;
      }

   }

}

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.stateexample.IShooterState;

   internal class ThreePointerState implements IShooterState{

      public function getAccuracy():Number {
         return 0.4;
      }

      public function getPointValue():Number {
         return 3;
      }

   }

}

The Shooter State Machine Class

The ShooterStateMachine class serves as a proxy to our state objects. It implements the same methods as our states and delegates the calls to the current state object. It is also responsible for switching states.

Tip

State machines are also sometimes referred to as the context in the State pattern.

Much like the SimpleShooter class, ShooterStateMachine has three constants that represent the three states. One of these three values should be used when calling the setState() method.

The setState() method has a little more functionally in this implementation of the basketball application. Instead of putting switch statements in each method, we put it in only the setState() method. The switch statement is responsible for creating the correct state object based on the state name.

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.stateexample.IShooterState;
   import com.peachpit.aas3wdp.stateexample.LayUpState;
   import com.peachpit.aas3wdp.stateexample.FreeThrowState;
   import com.peachpit.aas3wdp.stateexample.ThreePointerState;
    public class ShooterStateMachine {

       private var _state:IShooterState;
       public static const LAY_UP_STATE:String = "lay_up_state";
       public static const FREE_THROW_STATE:String = "free_throw_state";
       public static const THREE_POINTER_STATE:String = "three_pointer_state";

       public function ShooterStateMachine() {}

       public function getAccuracy():Number {
          return _state.getAccuracy();
       }

       public function getPointValue():Number {
          return _state.getPointValue(); 
       }

       public function setState(stateName:String):void {
          switch(stateName) {
             case LAY_UP_STATE:
                _state = new LayUpState();
                break;
             case FREE_THROW_STATE:
                __state = new FreeThrowState();
                break;
             case THREE_POINTER_STATE:
                _state = new ThreePointerState();
                break;
             default:
                _state = null;
           }
         }

      }

   }

The creation of state objects is done inside the setState() method. There are two options for creating and destroying state objects: The first is what we did in the preceding code. This is preferable because we create the states only when they are needed and we avoid creating states that are never used.

The second option is to create all the state objects in the constructor and simply change the reference to the current state. This second approach is valuable when you have to maintain some data across state switches. In the preceding example, the LayUpState object is re-created each time the state changes to LAY_UP_STATE. This is good for memory management, but what if we wanted to count how many lay-ups were made? If we used the same object each time that state was set, then a counter at the LayUpState level could persist across state changes. The second approach can also be good if your application has a lot of rapid state changing and you want to incur the performance hit of creating the objects only once.

Creating the Main Example Class

The main class for this implementation is nearly identical to the one for our first implementation. We’ll name this class ShooterImp; it will create an instance of ShooterStateMachine instead of SimpleShooter.

package {

   import flash.display.Sprite;
   import flash.utils.Timer;
   import flash.events.TimerEvent;
   import com.peachpit.aas3wdp.stateexample.ShooterStateMachine;

   public class ShooterExample extends Sprite {

      private var _points:Number;
      private var _shooter:ShooterStateMachine;

      public function ShooterExampe() {
        _points = 0;
        _shooter = new ShooterStateMachine();
        _shooter.setState(ShooterStateMachine.LAY_UP_STATE);
        var shotInterval:Timer = new Timer(1000, 10);
        shotInterval.addEventListener(TimerEvent.TIMER, onShot);
        shotInterval.start();
      }

      private function calculateShot(accuracy:Number):Boolean {
        return Math.random() < accuracy;
      }

      private function onShot(event:TimerEvent):void {
        if(calculateShot(_shooter.getAccuracy())) {
          _points += _shooter.getPointValue()
          trace("Made the Shot!  " + _points + " point(s)");
        }else {
          trace("Missed the Shot!")
          }
        }
      }

}

When you debug this implementation (ShooterExample), you’ll see that the result is exactly the same as SimpleShooterExample. However, we now have a design that is both scalable and easy to maintain.

Using Abstract Classes

As mentioned earlier in this chapter, there are many ways to implement a State pattern. In the previous example, we used an interface to define the methods that each state must implement. But what if we wanted to share a common implementation? One option would be to copy the implementation to each state class. This would work fine, but it would be difficult to manage as the number of states grew and change requests came in. A better option would be to use an abstract base class in addition to an interface. An abstract base class allows us to reuse a common implementation and still define methods that need to be overridden by each state class. In the next example, we’re going to encapsulate the shooting functionality inside each state as a shoot() method. The method is the same across all states; therefore, each state object does not override it. Figure 12.2 shows how the design changes to work with an abstract class in place of an interface.

Using abstract classes.

Figure 12.2. Using abstract classes.

The Abstract Shooter State

The biggest change between this example and the previous example is that we’re now using an abstract base class for all our state objects. We’re going to name this class AbstractShooterState.

Tip

It’s common convention to start abstract class names with the word Abstract. The naming convention is especially important for ActionScript classes because there is no programmatic way to enforce an abstract class.

The abstract class implements the private calculateShot() method from our original implementation class. This method is called from the shoot() method. The shoot() method uses the getAccuracy() and getPointValue() methods to calculate whether the shot was made and how many points the shot was worth. However, the getAccuracy() and getPointValue() methods are overridden by the state objects.

Note

This is actually an example of the Template Method pattern described in Chapter 5.

Notice that both the getAccuracy() and getPointValue() methods throw errors in this class (if they are not overridden, and someone tries to call one of the methods). And because the shoot() method depends on the results of getAccuray() and getPointValue(), the shoot() method would also throw the error. When these abstract methods are overridden, the errors are not thrown. This is a nice way to get around the fact that ActionScript 3.0 doesn’t officially support abstract classes.

Note

The state classes are made internal because they should be available only inside the com.peachpit.aas3wdp.stateexample package.

package com.peachpit.aas3wdp.stateexample {

   internal class AbstractShooterState {

       private function calculateShot(accuracy:Number):Boolean {
         return Math.random() < accuracy;
       }

       public function getAccuracy():Number {
          throw new Error("AbstractShooterState.getAccuracy() is an Abstract method 
                           and must be overridden.");
       }

       public function getPointValue():Number {
          throw new Error("AbstractShooterState.getPointValue() is an Abstract method
                           and must be overridden.");
       }

       public function shoot():Number {
          if(calculateShot(getAccuracy())) {
             trace("Made the Shot!");
             return getPointValue();
          }
          trace("Missed the Shot.");
          return 0;
       }

    }

}

State Objects

The state objects in this example vary only slightly from the original implementation. Instead of implementing the IShooterState interface, now the state classes extend the AbstractShooterState abstract class. Notice the use of the override keyword. It is necessary to use this keyword to override the methods of the base class. The method signatures must also match the base class’s methods exactly.

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.statepattern.AbstractShooterState;

   internal class LayUpState extends AbstractShooterState {

      public override function getAccuracy():Number {
         return 0.9;
      }

      public override function getPointValue():Number {
         return 2;
      }

     }

}

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.statepattern.AbstractShooterState;

   internal class FreeThrowState extends AbstractShooterState {

      public override function getAccuracy():Number {
         return 0.7;
      }

      public override function getPointValue():Number {
         return 1;
      }

   }

}

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.statepattern.AbstractShooterState;

   internal class ThreePointerState extends AbstractShooterState {

      public override function getAccuracy():Number {
         return 0.4;
      }

      public override function getPointValue():Number {
         return 3;
      }

   }

}

Although using an interface and an abstract class are both valid ways to implement the State pattern, each has its own use case. As a rule, you should use interfaces; however, if you have states that share an implementation, abstract classes are the better option.

The Shooter State Machine

The state machine changes only slightly for this example. We need to type the state property to the AbstractStateShooter abstract class instead of to the IShooterState interface. And we also need to implement a shoot() method that is delegated to the current state.

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.stateexample.AbstractShooterState;
    import com.peachpit.aas3wdp.stateexample.LayUpState;
    import com.peachpit.aas3wdp.stateexample.FreeThrowState;
    import com.peachpit.aas3wdp.stateexample.ThreePointerState;

    public class ShooterStateMachine {

       private var state:AbstractShooterState;
       public static const LAY_UP_STATE:String = "lay_up_state";
       public static const FREE_THROW_STATE:String = "free_throw_state";
       public static const THREE_POINTER_STATE:String = "three_pointer_state";

       public function ShooterStateMachine() {}

       public function getAccuracy():Number {
          return state.getAccuracy();
       }

       public function getPointValue():Number {
          return state.getPointValue(); 
       }

       public function shoot():Number {
          return state.shoot(); 
       }

       public function setState(stateDesc:String):void {
           switch(stateDesc) {
             case LAY_UP_STATE:
                state = new LayUpState();
                break;
             case FREE_THROW_STATE:
                state = new FreeThrowState();
                break;
             case THREE_POINTER_STATE:
                state = new ThreePointerState();
                break;
             default:
                state = null;
           }
       }

    }

 }

Creating the Main Example Class

The main class changes for this implementation because we encapsulated the shoot logic into the state objects. Therefore, we don’t need to calculate whether a shot was made—we only need to call the state object’s shoot() method, and that method tells us the points for that shot (0 for a missed shot and 1, 2, or 3 for a made shot).

package {

   import flash.display.Sprite;
   import flash.utils.Timer;
   import flash.events.TimerEvent;
   import com.peachpit.aas3wdp.stateexample.ShooterStateMachine;

   public class AbstractShooterExample extends Sprite {

      private var _points:Number;
      private var _shooter:ShooterStateMachine;

      public function AbstractShooterImp() {
         _points = 0;
         _shooter = new ShooterStateMachine();
         _shooter.setState(ShooterStateMachine.FREE_THROW_STATE);
         var shotInterval:Timer = new Timer(1000, 10);
         shotInterval.addEventListener(TimerEvent.TIMER, onShot);
         shotInterval.start();
      }

      private function onShot(event:TimerEvent):void {
         _points += _shooter.shoot();
         trace(_points);
      }

   }

}

Transitions

When Flash developers hear the word transistion, they first think of animations between screens or view states. However, in the context of the State pattern, a transition is simply what triggers our application to move from one state to another. This is not necessarily a visual thing, it’s simply how a state gets changed. You probably noticed in the previous examples that we had no way to move from one state to the other. The state was defined in the main class. In this section, we’ll look at two ways to handle transitions within the abstract state example we just completed.

Transitions Defined in the State Machine

The first implementation is the easiest. We set the current state in the state machine; therefore, why not also manage when that state changes? In this example, we switch the state after each shot. We do this by iterating through an array of the three state objects.

Our state machine is getting two new properties: _stateList and _index. The stateList property is an array that holds all the state names available to our state machine, and it holds them in the order in which they should be run. The index holds the current position of the state machine in the stateList array.

Inside the constructor, set the index to 0 and populate the stateList with the state names. Because we want the state machine to manage when the state is set, we’re removing the setState() call from our main class. Instead, we put the initial setState() call at the end of the state machine’s constructor.

The only other change is in the shoot() method. Now, instead of just returning the value of the current state’s shot, it also sets the next state.

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.stateexample.AbstractShooterState;
   import com.peachpit.aas3wdp.stateexample.LayUpState;
   import com.peachpit.aas3wdp.stateexample.FreeThrowState;
   import com.peachpit.aas3wdp.stateexample.ThreePointerState;

   public class ShooterStateMachine {

      private var _state:AbstractShooterState;
      private var _stateList:Array;
      private var _index:Number;
      public static const LAY_UP_STATE:String = "lay_up_state";
      public static const FREE_THROW_STATE:String = "free_throw_state";
      public static const THREE_POINTER_STATE:String = "three_pointer_state";

      public function ShooterStateMachine() {
         _index = 0;
         _stateList = new Array();
         _stateList.push(ShooterStateMachine.LAY_UP_STATE);
         _stateList.push(ShooterStateMachine.FREE_THROW_STATE);
         _stateList.push(ShooterStateMachine.THREE_POINTER_STATE);
         setState(_stateList[_index]);
      }

      public function getAccuracy():Number {
         return _state.getAccuracy();
      }

      public function getPointValue():Number {
         return _state.getPointValue(); 
      }

      public function shoot():Number {
         var shotResult:Number = state.shoot();
         if(++_index >= _stateList.length) _index = 0;
         setState(_stateList[_index]);
         return shotResult;
      }

      public function setState(stateDesc:String):void {
         switch(stateDesc) {
            case LAY_UP_STATE:
              _state = new LayUpState();
              break;
            case FREE_THROW_STATE:
              _state = new FreeThrowState();
              break;
            case THREE_POINTER_STATE:
              _state = new ThreePointerState();
              break;
            default:
              _state = null;
         }
       }
     }
   }

Transitions Defined in the State Objects

The other option for where to put the transition logic is a little more involved because it requires putting the transition logic inside the state objects. For this implementation, we’ll revert back to the state machine used in the original abstract state example, with two minor changes. We’re going to pass a reference to the state machine into each of the state objects’ constructors, and we’re going to set the first state in the state machine’s constructor.

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.stateexample.AbstractShooterState;
   import com.peachpit.aas3wdp.stateexample.LayUpState;
   import com.peachpit.aas3wdp.stateexample.FreeThrowState;
   import com.peachpit.aas3wdp.stateexample.ThreePointerState;

   public class ShooterStateMachine {

      private var _state:AbstractShooterState;
      public static const LAY_UP_STATE:String = "lay_up_state";
      public static const FREE_THROW_STATE:String = "free_throw_state";
      public static const THREE_POINTER_STATE:String = "three_pointer_state";

      public function ShooterStateMachine() {
         setState(ShooterStateMachine.LAY_UP_STATE);
       }

       public function getAccuracy():Number {
          return _state.getAccuracy();
       }

       public function getPointValue():Number {
          return _state.getPointValue(); 
       }

       public function shoot():Number {
          return _state.shoot();
       }

       public function setState(stateDesc:String):void {
          switch(stateDesc) {
             case LAY_UP_STATE:
               _state = new LayUpState(this);
               break;
             case FREE_THROW_STATE:
               _state = new FreeThrowState(this);
               break;
             case THREE_POINTER_STATE:
               _state = new ThreePointerState(this);
               break;
             default:
               _state = null;
         }

      }
   }

}

The state objects do most of the work for us. Each stores a reference to the state machine and calls its setState() method when the state object wants to move to the next state. We’re still triggering that move on the shoot() method call, so we need to override that method. We still call the abstract state’s shoot() method using the super keyword, but we need to override it so that we can add the setState() call to it. The following are the new state objects with these changes:

package com.peachpit.aas3wdp. stateexample {

   import com.peachpit.aas3wdp. stateexample.AbstractShooterState;

   internal class LayUpState extends AbstractShooterState {

      private var _stateMachine:ShooterStateMachine;

      public function LayUpState(_stateMachine:ShooterStateMachine) {
         this._stateMachine = _stateMachine;
      }

      public override function getAccuracy():Number {
         return 0.9;
      }

      public override function getPointValue():Number {
         return 2;

      }

      public override function shoot():Number {
         var pointValue:Number = super.shoot();
         _stateMachine.setState(ShooterStateMachine.FREE_THROW_STATE);
         return pointValue;
      }

    }

}

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.stateexample.AbstractShooterState;
   import com.peachpit.aas3wdp.stateexample.ShooterStateMachine;

   internal class FreeThrowState extends AbstractShooterState {

      private var _stateMachine:ShooterStateMachine;

      public function FreeThrowState(_stateMachine:ShooterStateMachine) {
         this._stateMachine = _stateMachine;
      }

      public override function getAccuracy():Number {
         return 0.7;
      }

      public override function getPointValue():Number {
         return 1;
       }

       public override function shoot():Number {
         var pointValue:Number = super.shoot();
         _stateMachine.setState(ShooterStateMachine.THREE_POINTER_STATE);
         return pointValue;
       }

    }

}

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.stateexample.AbstractShooterState;

   internal class ThreePointerState extends AbstractShooterState {

      private var _stateMachine:ShooterStateMachine;

      public function ThreePointerState(_stateMachine:ShooterStateMachine) {
         this._stateMachine = _stateMachine;
      }

      public override function getAccuracy():Number {
         return 0.4;

      }

      public override function getPointValue():Number {
         return 3;
      }

      public override function shoot():Number {
         var pointValue:Number = super.shoot();
         _stateMachine.setState(ShooterStateMachine.LAY_UP_STATE);
         return pointValue;
      }

        
  }

}

This logic could have gone into the abstract class, but it’s left on the state subclasses because it demonstrates the flexibility of defining the transitions this way. If you’re going to treat all the transitions the same, and you have a set methodology for how you trigger the transitions, then the state machine should manage that. However, if each state has different criteria for triggering a transition, defining the transition on the state object provides the most flexibility.

For example, maybe we want to change the way the LayUpState transitions. We’ll require a shooter to make a lay-up before we move on to the free throw. All we need to do is add a condition to the shoot() method. The following code shows the simple change:

package com.peachpit.aas3wdp.stateexample {

   import com.peachpit.aas3wdp.stateexample.AbstractShooterState;

   internal class LayUpState extends AbstractShooterState {

      private var _stateMachine:ShooterStateMachine;

      public function LayUpState(stateMachine:ShooterStateMachine) {
         this._stateMachine = _stateMachine;
      }

      public override function getAccuracy():Number {
         return 0.9;
      }

      public override function getPointValue():Number {
         return 2;
      }

      public override function shoot():Number {
         var pointValue:Number = super.shoot();
         if(pointValue > 0) {
           _stateMachine.setState(ShooterStateMachine.FREE_THROW_STATE);
         }
         return pointValue;
      }

    }
}

Summary

The State pattern is a great way to manage states within your application. By encapsulating the states into their own objects, we make the code more scalable and maintainable. There are many ways to implement the State pattern. In this chapter, we looked at how to build states using both an interface and an abstract class; and we discussed when to use each of these approaches.

Transitions are the action of switching states. They can be defined in the context or inside the state objects. Although defining transitions inside the state objects gives you more flexibility, doing so can produce code that is difficult to maintain.

..................Content has been hidden....................

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