O'Reilly logo

ActionScript 3.0 Design Patterns by Chandima Cumaranatunge, William Sanders

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Dynamic Selection of Concrete Components and Decorations: a Hybrid Car Dealership

Up to this point, the examples have focused on different dimensions of the Decorator design pattern, with the emphasis on how the different elements in the Decorator design pattern can be used with different components and decorations. Both concrete and abstract output has shown different ways to display information, but no example has illustrated how to input data dynamically. This Decorator example uses the case of selecting automobiles and their options to illustrate how to dynamically input data for both decorators and concrete components.

Imagine that you are responsible for creating and maintaining a web site for a car dealership. With each year, new models appear, and different options are made available. You never know what options are going to be added or dropped, or even if the same car models will be around from one year to the next. You've decided to use the Decorator pattern because you can easily add or drop both concrete components and concrete decorators. The concrete components will be the different models, and the options are the different decorations for any model selected. So whenever a new model appears, you simply update the concrete component to reflect those changes. Likewise with the options available for any of the models, all you need to change are the concrete decorations. You can easily add or change decorations without altering the program's basic structure.

Setting Up the Hybrid Car Components

This particular dealership has decided to specialize in four hybrid model cars. This example uses four such cars, the Prius, Mercury Mariner, Ford Escape and Honda Accord hybrids. So in addition to an abstract Component class, this example requires four concrete components.

Auto abstract component

To get started, the abstract component is cast as a class named Auto. It needs only a string for the name of the car, and a numeric variable for the car's price. Two getter functions for auto type and price make up the rest of the abstract component. Example 4-38 shows the code saved as Auto.as.

Example 4-38. Auto.as

package
{
    //Abstract class
    public class Auto
    {
        protected var information:String;
        protected var bucks:Number;

        public function getInformation():String
        {
            return information;
        }
        public function price():Number
        {
            return bucks;
        }
    }
}

As with all Decorator design patterns, all other classes are derived from this abstract component class. In the next section, you'll see that all of the concrete component classes are subclasses of the Auto class.

Hybrid car classes concrete component

The four hybrid autos are placed into separate concrete component classes that are all subclassed from the Auto class. The constructor function assigns a value to the information property, and the price() function returns a value representing the car's price. In Example 4-39 to Example 4-42, the captions are the filenames to use for saving each class.

Example 4-39. Prius.as

package
{
    public class Prius extends Auto
    {
        public function Prius()
        {
            information = "Toyota Prius Hybrid~\n";
        }
        override public function price():Number
        {
            return 21725.00;
        }
    }
}

Example 4-40. Mariner.as

package
{
    public class Mariner extends Auto
    {
        public function Mariner()
        {
            information = "Mercury Mariner Hybrid~\n";
        }
        override public function price():Number
        {
            return 29225.00;
        }
    }
}

Example 4-41. Accord.as

package
{
    public class Accord extends Auto
    {
        public function Accord()
        {
            information = "Accord Hybrid~\n";
        }
        override public function price():Number
        {
            return 30990.00;
        }
    }
}

Example 4-42. Escape.as

package
{
    public class Escape extends Auto
    {
        public function Escape()
        {
            information = "Ford Escape Hybrid\n~";
        }
        override public function price():Number
        {
            return 26240.00;
        }
    }
}

The prices here are based on prices found on the Internet, but they may or may not reflect prices at a later date. If you want to update prices or even use different autos, feel free to do so.

The \n escape character and the tilde (~) characters are used for formatting purposes. The \n is a line break, and the tilde (~) helps in separating out all the different models that become a big string as the decorator merges them altogether. Without these, the output would be a mess.

Using Auto Options as Decorators

With all of the concrete components in place, the next step will be to construct the decorator class and the concrete decorators. Like the components they decorate, they too will need an identification and price property as well as methods to return them. The abstract decorator will set that up so that the derived classes have the necessary properties and methods.

The options abstract decorator

As you have seen with other abstract decorator classes in this chapter, it's one of the simplest classes. Because it extends the abstract concrete component class, it inherits all of the class' properties. However, we need to re-implement the getter function so that we can further re-implement it for the delegations the different concrete decorators use. Example 4-43 shows the abstract decorator class to be saved as Decorator.as.

Example 4-43. Decorator.as

package
{
    //Abstract class
    public class Decorator extends Auto
    {
        override public function getInformation():String
        {
            return information;
        }
    }
}

Because the information variable is inherited from the Auto class, we need not redefine it here. It represents an abstract string.

The options concrete decorators

The concrete decorators generate the information that adds the information property and price value to each option. As a concrete component is wrapped in each, the string data are added to any other strings that wrap the component. So, when the getInformation() method launches, it first gets the delegated information from all other options and the concrete component it wraps. In order not to get a huge string that we cannot unravel, a tilde (~) on the end of the added string will help separate all of the different decorations. Example 4-44 to Example 1-47 are labeled with the filenames used to save the class.

Example 4-44. HeatedSeat.as

package
{
    public class HeatedSeats extends Decorator
    {
        private var auto:Auto;
        public function HeatedSeats(auto:Auto)
        {
            this.auto=auto;
        }
        override public function getInformation():String
        {
            return auto.getInformation() + " Heated Seats~";
        }
        override public function price():Number
        {
            return 350.78 + auto.price();
        }
    }
}

Example 4-45. GPS.as

package
{
    public class GPS extends Decorator
    {
        private var auto:Auto;
        public function GPS(auto:Auto)
        {
            this.auto=auto;
        }
        override public function getInformation():String
        {
            return auto.getInformation() +
                  " Global Positioning System~";
        }
        override public function price():Number
        {
            return 345.88 + auto.price();
        }
    }
}

Example 4-46. RearViewVideo.as

package
{
    public class RearViewVideo extends Decorator
    {
        private var auto:Auto;
        public function RearViewVideo(auto:Auto)
        {
            this.auto=auto;
        }
        override public function getInformation():String
        {
            return auto.getInformation() + " Rear View Video~";
        }
        override public function price():Number
        {
            return 560.75 + auto.price();
        }
    }
}

Example 4-47. MP3.as

package
{
    public class MP3 extends Decorator
    {
        private var auto:Auto;
        public function MP3(auto:Auto)
        {
            this.auto=auto;
        }
        override public function getInformation():String
        {
            return auto.getInformation() + " MP3 Player~";
        }
        override public function price():Number
        {
            return 267.55 + auto.price();
        }
    }
}

For this particular application, we're not concerned with separating the individual costs. In fact, we want each cost to be accumulated with the others, including the cost of the concrete component we're decorating. So while the string value needs the tilde (~) for demarcation purposes, we don't need it for the numeric value.

Setting Up the User Interface

The largest single class we're going to use is the one to create the user interface. The line numbers appear in the Example 4-48 for the purpose of referencing lines in the code. A lot of the work done by this class, named Deal, is to set up the interface objects. These include the radio button, checkbox, and button components. Additionally, a good hunk of code is required for the text field output window and formatting for the output. So while the interface to the decorator pattern may look unwieldy, it's not decorator pattern's fault. In fact, only the getCar() (beginning on line 110) and getOptions() (beginning on line142) private functions are employed to pull out the information generated by the Decorator pattern.

To get a handle on what the class does, enter the code from Example 4-48 and save it as Deal.as.

Example 4-48. Deal.as

1 package
2 {
3     import fl.controls.CheckBox;
4     import fl.controls.RadioButton;
5     import fl.controls.Button;
6     import flash.display.Sprite;
7     import flash.display.MovieClip;
8     import flash.events.MouseEvent;
9     import flash.text.TextField;
10    import flash.text.TextFormat;
11
12    public class Deal extends Sprite
13    {
14        internal var checks:Array=[];
15        internal var cars:Array=[];
16        internal var carDeal:Auto;
17        public var dealText:TextField=new TextField();
18
19        //Constructor Function
20        public function Deal ():void
21        {
22            getRadios ();
23            getChecks ();
24            doDealButton ();
25            showDeal ();
26        }
27        //Add button from Library
28        private function doDealButton ():void
29        {
30            var doDeal:Button=new Button();
31            this.addChild (doDeal);
32            doDeal.x=215;
33            doDeal.y=195;
34            doDeal.label="Make Deal";
35            doDeal.addEventListener (MouseEvent.CLICK,getPackage);
36        }
37        //**
38        //Get information from Decorator and display it
39        //**
40        private function getPackage (e:MouseEvent):void
41        {
42            getCar ();
43            getOptions ();
44            if (carDeal == null)
45            {
46                return;
47            }
48            else
49            {
50                var nowDrive:String=carDeal.getInformation()+"\nTotal=
 $"+carDeal.price();
51            }
52            dealText.text=formatMachine(nowDrive);
53        }
54        //Format Output
55        private function formatMachine (format:String):String
56        {
57            if (format.indexOf("~") != −1)
58            {
59                format=format.split("~").join("\n");
60            }
61            return format;
62        }
63        //Text Field & Format
64        private function showDeal ():void
65        {
66            dealText.width=150;
67            dealText.height=100;
68            dealText.wordWrap=true;
69            dealText.multiline=true;
70            dealText.x=165;
71            dealText.y=230;
72            dealText.border=true;
73            dealText.borderColor=0xcc0000;
74            var dealerFormat:TextFormat=new TextFormat();
75            dealerFormat.leftMargin=4;
76            dealerFormat.rightMargin=4;
77            dealText.defaultTextFormat=dealerFormat;
78            this.addChild (dealText);
79        }
80        //Add Check boxes for Options (Concrete Decorators)
81        private function getChecks ():void
82        {
83            var gizmos:Array=new Array("MP3","Heated Seats",
"GPS", "Rear View Video");
84            var saloon:uint=gizmos.length;
85            var giz:uint;
86            for (giz=0; giz<saloon; giz++)
87            {
88                checks[giz]=new CheckBox();
89                this.addChild (checks[giz]);
90                checks[giz].width=150;
91                checks[giz].x=250;
92                checks[giz].y=80+(giz*30);
93                checks[giz].label=gizmos[giz];
94            }
95        }
96        //Add Radio buttons Auto (Concrete Components)
97        private function getRadios ():void
98        {
99            var car:Array=new Array("Escape","Mariner","Prius","Accord");
100           var saloon:uint=car.length;
101           var ride:uint;
102           for (ride=0; ride<saloon; ride++)
103           {
104               cars[ride]=new RadioButton();
105               cars[ride].groupName="deals";
106               this.addChild (cars[ride]);
107               cars[ride].x=150;
108               cars[ride].y=80+(ride*30);
109               cars[ride].label=car[ride];
110           }
111       }
112       //Select Auto and create Concrete Component
113       private function getCar ():void
114       {
115           var tracker:String;
116           var hybrid:uint;
117           for (hybrid=0; hybrid<cars.length; hybrid++)
118           {
119               if (cars[hybrid].selected)
120               {
121                   tracker=cars[hybrid].label;
122                   switch (tracker)
123                   {
124                       case "Escape" :
125                           carDeal = new Escape();
126                           break;
127
128                       case "Mariner" :
129                           carDeal = new Mariner();
130                           break;
131
132                       case "Prius" :
133                           carDeal = new Prius();
134                           break;
135
136                       case "Accord" :
137                           carDeal = new Accord();
138                           break;
139                   }
140               }
141           }
142       }
143       //Select options -- wrap Concrete Component in Decorator
144       private function getOptions ():void
145       {
146           var tracker:String;
147           var toy:uint;
148           for (toy=0; toy<checks.length; toy++)
149           {
150               if (checks[toy].selected)
151               {
152                   tracker=checks[toy].label;
153                   switch (tracker)
154                   {
155                       case "MP3" :
156                           carDeal = new MP3(carDeal);
157                           break;
158
159                       case "Heated Seats" :
160                           carDeal = new HeatedSeats(carDeal);
161                           break;
162
163                       case "GPS" :
164                           carDeal = new GPS(carDeal);
165                           break;
166
167                       case "Rear View Video" :
168                           carDeal = new RearViewVideo(carDeal);
169                   }
170               }
171           }
172       }
173   }
174 }
175

Creating the document and setting the stage

Once you've saved the Deal.as file, you'll need to do a little work on a Flash document file. The following steps will guide you.

  1. Open a new Flash document and save it as AutoDealer.fla in the same folder as the .as files.

  2. In the Document class window, type in Deal, and resave the file.

  3. Using Figure 4-8 as a guide, use Static text to add the header "2 Guys From Connecticut Hybrid Cars." Beneath the header, at horizontal position 150, type in "Select Car," and on the same line at horizontal position 250, type in "Select Options."

  4. Open the Components and Library panels and drag a copy of the radio button and the checkbox to the Library.

  5. Select Insert→Symbol in the menu bar, and select Button as Type. In the Name window, type in Button (which is the default name), and click the Import for ActionScript checkbox. Click OK to enter the Button Symbol editor indicated by the special four-frame Timeline.

  6. In the Button Symbol editor, add a layer, and, selecting the top layer, use the Rectangle Tool to draw a rectangle with colors to suit your tastes with 8-point rounded corners located at x=0, y=0. Size it to W=43, H=13. Add keyframes to all of the frames in the top layer. Select the object in the Over frame and swap (reverse) the stroke and fill colors.

  7. Add keyframes to the first three frames in the lower layer. In the Up frame, using a Static text and narrow sans serif 10-point font type, type "Calculate" centered below the button. In the Over frame, do the same thing, but type in "Total Cost." Leave the Down frame empty.

  8. Check to make sure your Library shows the following: CheckBox, CheckBoxSkins, Component Assets, Button, RadioButton, RadioButtonSkins. If it does, you should be all set.

The next step is to test the application. All the buttons and the text field are automatically placed on the stage. Figure 4-8 shows what you can expect to see if everything is working as expected:

Decorator user interface and output

Figure 4-8. Decorator user interface and output

Implementing the concrete components and their decorators

The pattern implementation process requires that certain things are in place so that the user can choose what she wants as far as both concrete components (car model) and concrete decorators are concerned. Initially, the script must first find which car has been selected. So the function getCar() loops through the radio buttons until it finds which one is selected. When it locates it, it simply assigns an instance of carDeal with one of the concrete components such as

carDeal = new Mariner();

The carDeal object has to be instantiated outside a function so that it can be used by more than a single private function. On line 15 of Example 4-48, the carDeal object is instantiated as an Auto data type. (The internal statement is added to draw attention to the fact that the variable is available to any caller in the same package—even though it is the default state when instantiating variables.)

Once the carDeal object has been created, the script looks at the options selected. Using the getOptions() function, each of the checkboxes is compared to its selection state. When a selected option is found, the option, a concrete decorator, wraps the concrete object carDeal. For example, if one of the selections is heated seats, the code section in the switch statement looks like the following:

case "Heated Seats":
carDeal = new HeatedSeats(carDeal);
break;

Because more than one decorator can be selected, as each selected option is located, the concrete object can be wrapped more than once. Thus, any or all of the options can be added to the selected auto.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required