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.

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 madewe 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);       }    } }





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