Building a Simple Example


In this example, we'll build a clock model with two viewsanalog and digital. We'll start by building the clock model, which we'll call ClockData. Then we'll build the AnalogClock view. After the analog clock view works in conjunction with the data model, we can build a second view to see how simple it is to use the same model for two or more views. And, to prove how simple it is to change the model without affecting the views (so long as the model interface remains the same), we will update ClockData to handle additional responsibilities.

Clock Data Model

The ClockData class stores essentially one piece of datathe time. To store the time, we'll construct a simple building-block class called Time which has the accessor getter and setter methods for hour, minute, and second properties. We can define the Time class as follows:

package com.peachpit.aas3wdp.mvcexample.data {    public class Time {       private var _hour:uint;       private var _minute:uint;       private var _second:uint;       public function get hour():uint {          return _hour;       }       public function set hour(value:uint):void {          _hour = value;       }       public function get minute():uint {          return _minute;       }       public function set minute(value:uint):void {          _minute = value;       }       public function get second():uint {          return _second;       }       public function set second(value:uint):void {          _second = value;       }       public function Time(hour:uint, minute:uint, second:uint) {          _hour = hour;          _minute = minute;          _second = second;       }       public function clone():Time {          return new Time(_hour, _minute, _second);       }    } }


Note that the Time class also defines a clone() method that returns a new Time object with the same time value as the original. The clone() method is important so that we can assign clones of Time objects rather than references to the original.

Next we want to define the ClockData class. We'll modify the ClockData class later so that it is more robust and takes on more responsibilities. However, to start, the ClockData class will simply act as a wrapper for a Time object. The following is the definition for ClockData:

package com.peachpit.aas3wdp.mvcexample.data {    import flash.events.Event;    import flash.events.EventDispatcher;    import com.peachpit.aas3wdp.mvcexample.data.Time;    public class ClockData extends EventDispatcher {       private var _time:Time;       public function get time():Time {          if(_time == null) {             var date:Date = new Date();             return new Time(date.hours, date.minutes,              date.seconds);          }          else {             return _time.clone();          }       }       public function set time(value:Time):void {          _time = value.clone();          dispatchEvent(new Event(Event.CHANGE));       }       public function ClockData() {       }    } }


As you can see in this ClockData class, there is one getter/setter accessor method pair called time. The time setter simply allows you to assign a clone of a Time object to the _time property. The getter method returns a clone of the Time object if it's defined. If not, it returns a new Time object with the current client clock time retrieved from a Date object.

In both cases where the ClockData class uses the clone() method of Time objects, it does so to protect encapsulation. It's important to understand that you don't always have to protect encapsulation at this level, but we demonstrate this concept here to point out the implications of using clones of objects rather than references. In this case, if the setter method assigned a reference rather than a clone of the parameter, then any change to the original object used as the parameter would also affect the data model. Likewise, and perhaps more importantly, the getter method also calls clone() to return a clone of the Time object. If it didn't call clone(), but returned a reference to the object, then any changes to that object outside the data model would also affect the data model. Consider the following example:

var data:ClockData = new ClockData(); var time:Time = new Time(12, 0, 0); data.time = time; time.hour = 14; trace(data.time.hour);


In this example, the TRace() statement would output 12, which is what we would probably expect. But if the setter assigned a reference rather than a clone to the _time property of the ClockData object, then the trace() statement would output 14. The same is true for the getter, as shown here:

var data:ClockData = new ClockData(); var time:Time = new Time(12, 0, 0); data.time = time; var timeValue:Time = data.time; timeValue.hour = 20; trace(data.time.hour);


In this example, if the getter returns a clone, then the trace() statement will output 12. If the getter returns a reference, then the trace() statement would output 20.

Analog Clock View

Now that we've built the model, we can build one of the viewsthe analog clock view. Because we know ahead of time that we're going to build more than one clock view, it makes sense to first determine whether there is any common functionality we can place in an abstract base class. Doing so provides two benefits: It results in less redundant code among clock view classes inheriting from the abstract base class, and it enables polymorphism whereby we can type variables as the abstract base class so that any of the concrete types can be substituted.

We'll call the abstract base class Clock, and this class constructor will require one parameter specifying the model (a ClockData object) to use. It will store that model in a protected instance property, and it will register to listen for change events. Here's the code for the base class called AbstractClockView:

package com.peachpit.aas3wdp.mvcexample.clock {    import flash.display.Sprite;    import com.peachpit.aas3wdp.mvcexample.data.ClockData;    import flash.events.Event;    public class AbstractClockView extends Sprite {       protected var _data:ClockData;       public function Clock(data:ClockData) {          _data = data;          _data.addEventListener(Event.CHANGE, draw);       }                protected function draw(event:Event):void {       }    } }


Notice that _data is declared as protected so that it is accessible to all subclasses. Also note that because the constructor adds a listener to the ClockData object, the class must declare the listener method, draw(). The draw() method is declared as protected as well because subclasses must be able to override the method to define the specific implementation.

Next we can define AnalogClock, a concrete subclass of AbstractClockView. The analog clock view consists of a clock face as well as the hour hand, minute hand, and second hand. The AnalogClock class is defined as follows:

package com.peachpit.aas3wdp.mvcexample.clock {    import com.peachpit.aas3wdp.mvcexample.data.ClockData;    import com.peachpit.aas3wdp.mvcexample.data.Time;    import flash.display.Sprite;    import flash.events.Event;    public class AnalogClock extends AbstractClockView {       private var _face:Sprite;       private var _hourHand:Sprite;       private var _minuteHand:Sprite;       private var _secondHand:Sprite;       public function AnalogClock(data:ClockData) {          // Call the super constructor, passing it the          // model parameter.          super(data);          // Create the clock face, and draw a circle.          _face = new Sprite();          _face.graphics.lineStyle(0, 0x000000, 1);          _face.graphics.drawCircle(0, 0, 100);          addChild(_face);          // Create the hands.          _hourHand = new Sprite();          _hourHand.graphics.lineStyle(5, 0x000000, 1);          _hourHand.graphics.lineTo(0, -50);          addChild(_hourHand);          _minuteHand = new Sprite();          _minuteHand.graphics.lineStyle(2, 0x000000, 1);          _minuteHand.graphics.lineTo(0, -80);          addChild(_minuteHand);          _secondHand = new Sprite();          _secondHand.graphics.lineStyle(0, 0x000000, 1);          _secondHand.graphics.lineTo(0, -80);          addChild(_secondHand);          // Call the draw() method to draw the initial view.          draw();       }       // Override the draw() method. This method gets called once       // when the object is constructed, and then it gets called       // every time the model dispatches a change event.       override protected function draw(event:Event = null):void {          var time:Time = _data.time;          // Set the rotation of the hands based on the time          // values.          _hourHand.rotation = 30 * time.hour + 30 *           time.minute / 60;          _minuteHand.rotation = 6 * time.minute + 6 *           time.second / 60;          _secondHand.rotation = 6 * time.second;       }    } }


Testing the Analog Clock

The remaining step in the first part of this exercise is to see whether the analog clock really works. For this purpose, we'll create a simple main class that creates an instance of the model and an instance of the view that uses the model:

package {    import flash.display.Sprite;    import flash.display.StageAlign;    import flash.display.StageScaleMode;    import com.peachpit.aas3wdp.mvcexample.data.ClockData;    import com.peachpit.aas3wdp.mvcexample.clock.AbstractClockView;    import com.peachpit.aas3wdp.mvcexample.clock.AnalogClock;    public class ClockTest extends Sprite {       private var _clockData:ClockData;       public function ClockTest() {          stage.align = StageAlign.TOP_LEFT;          stage.scaleMode = StageScaleMode.NO_SCALE;          _clockData = new ClockData();          var clock:AbstractClockView = new            AnalogClock(_clockData);          clock.x = 100;          clock.y = 100;          addChild(clock);       }    } }


When you test this application, you'll see an analog clock appear displaying the current time.

Now let's modify the main class slightly so that it uses a timer to update the time property of the model after 2 seconds. Updating the model value will cause it to dispatch a change event which will, in turn, cause the view to redraw. The following code initially displays the clock with the current time, and then 2 seconds later it will display 5 o'clock:

package {    import flash.display.Sprite;    import flash.display.StageAlign;    import flash.display.StageScaleMode;    import com.peachpit.aas3wdp.mvcexample.data.ClockData;    import com.peachpit.aas3wdp.mvcexample.clock.AbstractClockView;    import com.peachpit.aas3wdp.mvcexample.clock.AnalogClock;    import com.peachpit.aas3wdp.mvcexample.data.Time;    import flash.utils.Timer;    import flash.events.TimerEvent;    public class ClockTest extends Sprite   {       private var _clockData:ClockData;       public function ClockTest() {          stage.align = StageAlign.TOP_LEFT;          stage.scaleMode = StageScaleMode.NO_SCALE;          _clockData = new ClockData();          var clock:AbstractClockView = new            AnalogClock(_clockData);          clock.x = 100;          clock.y = 100;          addChild(clock);                              var timer:Timer = new Timer(2000, 1);          timer.addEventListener(TimerEvent.TIMER, onTimer);          timer.start();                           }       private function onTimer(event:TimerEvent):void {          _clockData.time = new Time(5, 0, 0);       }    }  }


Digital Clock View

Now that the analog clock view works, the next step is to build a digital clock view. The DigitalClock class, like the AnalogClock class, is a subclass of AbstractClockView. Define the class as follows:

package com.peachpit.aas3wdp.mvcexample.clock {    import com.peachpit.aas3wdp.mvcexample.data.ClockData;    import com.peachpit.aas3wdp.mvcexample.data.Time;    import flash.display.Sprite;    import flash.events.Event;    import flash.text.TextField;    import flash.text.TextFieldAutoSize;        public class DigitalClock extends AbstractClockView {              private var _frame:Sprite;       private var _display:TextField;              public function DigitalClock(data:ClockData) {          // Call the super constructor, passing it the          // data parameter.          super(data);          // Draw a 200 by 50 pixel rectangular frame.          _frame = new Sprite();          _frame.graphics.lineStyle(0, 0x000000, 1);          _frame.graphics.drawRect(0, 0, 200, 50);          addChild(_frame);          // Add a text field.          _display = new TextField();          _display.width = 200;          _display.height = 50;          _display.autoSize = TextFieldAutoSize.RIGHT;          _display.x = 195;          _display.y = 5;          addChild(_display);          // Call draw() when the object is constructed.          draw();       }              // Override the draw() method.       override protected function draw(event:Event = null):void {          var time:Time = _data.time;          // Display the hour, minute, and second in the          // text field. Use the zeroFill() method to ensure          // that the minute and second values are always          // two digits (e.g. 1 displays as 01.)          _display.htmlText = "<font face='_typewriter'            size='40'>" + time.hour + ":" +            zeroFill(time.minute) + ":" + zeroFill(time.second)            + "</font>";       }       private function zeroFill(value:Number):String {          if(value > 9) {             return value.toString();          }          else {             return "0" + value;          }       }           } }


You'll probably notice that DigitalClock is very similar to AnalogClock. The only difference is that it displays the value using a text field rather than a group of hands. Because DigitalClock extends AbstractClockView, it too automatically receives event notifications when the model changes.

Testing the Digital Clock

Because polymorphism is enabled for AnalogClock and DigitalClock on account of their common, inherited interface, you can substitute a DigitalClock for an AnalogClock very easily. To test the digital clock view, modify the main class by importing the DigitalClock class and using a DigitalClock constructor rather than an AnalogClock constructor, as shown here:

package {    import flash.display.Sprite;    import flash.display.StageAlign;    import flash.display.StageScaleMode;    import com.peachpit.aas3wdp.mvcexample.data.ClockData;    import com.peachpit.aas3wdp.mvcexample.clock.AbstractClockView;    import com.peachpit.aas3wdp.mvcexample.clock.AnalogClock;    import com.peachpit.aas3wdp.mvcexample.clock.DigitalClock;    import com.peachpit.aas3wdp.mvcexample.data.Time;    import flash.utils.Timer;    import flash.events.TimerEvent;    public class ClockTest extends Sprite   {       private var _clockData:ClockData;       public function ClockTest() {                    stage.align = StageAlign.TOP_LEFT;          stage.scaleMode = StageScaleMode.NO_SCALE;                    _clockData = new ClockData();          var clock:AbstractClockView =            new DigitalClock(_clockData);          clock.x = 100;          clock.y = 100;          addChild(clock);          var timer:Timer = new Timer(2000, 1);          timer.addEventListener(TimerEvent.TIMER, onTimer);          timer.start();                 }              private function onTimer(event:TimerEvent):void {          _clockData.time = new Time(5, 0, 0);       }        } }





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