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.

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.




Advanced ActionScript 3 with Design Patterns
Advanced ActionScript 3 with Design Patterns
ISBN: 0321426568
EAN: 2147483647
Year: 2004
Pages: 132

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net