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

Chapter 4. Decorator Pattern

I try to decorate my imagination as much as I can.

Franz Schubert

Pictures deface walls more often than they decorate them.

William Wordsworth (Poet and a guy who really understood bandwidth)

What is the Decorator Pattern?

The Decorator pattern wasn’t developed with a web designer or developer in mind, but it could well have been. A few years back, we developed a web site and were cognizant of the fact that periodically we’d have to update it. The design was set up so that substitution of one element for an updated one was fairly simple, and it required no major change in the code or structure. Because we didn’t have to update it too often, this wasn’t much of a problem. However, had we needed to update elements or features on a fairly regular basis, our design would have left a good deal to be desired.

Imagining situations where you need to update or change certain parts of a web site on a regular basis isn’t difficult. If your client is a retailer with regular advertising such as weekly specials and new products introduced periodically, you want to have flexibility in your web design and structure. You may want to use features of your basic structure that assure change is easily accommodated, but you don’t have to alter the basic structure itself in any way.

The Decorator pattern addresses the issue of maintaining the structure while having the ability to make changes by decorating the different components that make up the application. The decorations are composed of descriptions and/or methods used to wrap different objects in the application. As you will see, this design pattern allows you to mix and match all the different components and decorations to optimize flexibility and expandability, while core structure classes remain unaltered.

Key Features

We can understand the Decorator pattern in terms of two key features. Often, developers want to add unique responsibilities for an object without adding those same responsibilities to the whole class. Among other design patterns, the Decorator pattern’s characterized by adding unique responsibilities. The identifying characteristic of the Decorator pattern is to add responsibilities in a uniquely Decorator fashion. Wrapping a component in an object that adds a responsibility follows a couple of guidelines:

  • Decorators can appear wherever a component object can.

  • At runtime, you can mix and match combinations of decorators as needed.

To understand the Decorator design pattern’s key features, you need to consider some alternatives to implementing the work the pattern does. Essentially, your project requires that you add new features and responsibilities to individual objects rather than the entire class. To do so using inheritance would bloat the class and change the structure with each new feature. Every single object would inherit all of the features and functionality of every other object, and that’s not what you want.

Note

In this chapter, you will see the term “component” a good deal. The reference to component here is wholly unrelated to the components in Flash, used for UIs, Media, Data and other purposes. In the context of this chapter, a component refers to a concrete instance that is decorated with another concrete instance called a decorator. So, for the time being, don’t think of components as anything other than something that gets decorated. (In the last application example of a Decorator design pattern in this chapter, you’ll be using Flash UI components, but by then you’ll be able to distinguish the different kind of components.)

Imagine you’re setting up an automobile dealership site. You can choose between different models of autos and add different features—options. You can set up options such as an MP3 player, Global Positioning System (GPS), cloth, vinyl or leather seat covers, and different kinds of alarm systems. If you use inheritance, every one of those options would have to be in every object. What’s more, you’d need to have all the models in your main class as well. That’s absurd! Why would anyone need both cloth and vinyl seats or be both a Ford Escape and a Chevrolet Malibu? Then, if a new option were introduced, you’d have to bloat the class with yet another option for every single object. However, if you can just wrap a single responsibility around a component when and if you need it, you can keep your program slim, trim and ready to adapt.

The key to understanding the Decorator design pattern is to understand that it uses inheritance and employs abstract classes; however, as you know from Chapter 2, ActionScript 3.0 doesn’t support abstract classes. You can create classes that work like abstract classes, simply by not instantiating them directly. In fact, that’s what an abstract class is to some extent—a class that you do not instantiate but can extend. They work something like an interface, but while you can implement an interface and its abstract methods, you cannot extend it. (You were introduced to using interfaces in Chapter 1, and in Chapter 5, you will be using a design pattern that employs interfaces.)

Note

Lack of support of abstract classes is a sore point with some Flash and Flex developers. Before firing off an impassioned email to Adobe, though, first take a look at the ECMAScript Rev 4 specs. These specifications don’t exactly support abstract classes either, at this point in time.

Why are abstract classes important for the Decorator pattern? Like interfaces, you can create abstract methods that can be implemented in different ways. At the same time, you can use them to create subclasses so that core properties can be inherited in ways not possible with interfaces alone.

The Decorator Model

The Decorator outlined in the class diagram notation shows two key elements: component and decorator. The component represents what’s to be decorated, and the decorator is the abstract class for the concrete decorations. The concrete component is what’s actually decorated, and the concrete decorations are the actual decorations. All of the concrete decorations and the concrete component are subclassed from the same source. Keeping that in mind, take a look at the class diagram in Figure 4-1:

Decorator design pattern
Figure 4-1. Decorator design pattern

In the most basic sense, the component is the Christmas tree, and the decorations are the ornaments. Each concrete decorator wraps the tree and is subclassed from the same source as the tree. A better way to think about components and decorators is more like the nesting dolls from Japan and Russia. The innermost doll is the component, and it’s placed into a decorator. Then the component and decorator are placed into another decorator, and then into the next decorator. Figure 4-2 shows this model.

Component wrapped in series of decorators
Figure 4-2. Component wrapped in series of decorators

Probably the most important thing to remember about the Decorator pattern is that its purpose is to allow additional elements to be added to a class without altering the base class. So instead of making changes by changing the class, the class is decorated with subclasses that don’t interfere with the class from which the decorators were derived.

Key OOP Concepts Used with the Decorator Pattern

Sometimes when you think about key OOP concepts such as inheritance, you have to consider its larger consequences. The upside of inheritance is that once a superclass has been established, all of the subclasses inherit its features. However, you may not want or need all of the “stuff” for every subclass from a superclass. If you want to add functionality to an object, subclassing may not be the way to go, because everything else subclassed from the same superclass may be unnecessarily burdened by unused functionality.

Unwanted Inheritance

At the end of this chapter, you will see an example of a car dealership where users select models of cars with a choice of options for those cars. If all the options were derived from a superclass, that would mean that every car would have all the options, whether or not you wanted them. One option in the example is a rear view camera used with minivans and large SUVs with limited rear vision. Such an option would be superfluous on a sports car such as a Chevrolet Corvette, Morgan, or Ferrari. However, with subclassing, that’s exactly what would happen. Every subclass gets everything from the superclass. So here’s a case where we’d have to look beyond simple inheritance. Depending on how the application’s written, even the components would all have the same features. A pickup truck would be subclassed from the same component as a luxury car—both inheriting features they don’t want and wouldn’t use.

Wrapping Responsibilities

The Decorator design pattern is also known as the Wrapper pattern. The concept of “wrapping” is at the heart of the Decorator design pattern. So what does it mean to “wrap” one object in another? One way to think about wrapping is to imagine wrapping a gift. The wrapper transforms the gift, but the gift does not inherit the characteristics of the wrapper—it only uses the wrapping. Unlike subclassing, which extends one class into another, wrapping allows one object to use another’s characteristics without extending either the wrapped object or the object doing the wrapping.

The wrapping with which most ActionScript programmers are familiar is that used for transforming data types from one type to another. For example, the Number class can wrap a string variable, and then that variable has the characteristics of the Number class. Figure 4-3 illustrates a common wrapper function:

Number class wrapping String class
Figure 4-3. Number class wrapping String class

Note

When we look at wrapping one object with another object, we can think of it as a class intercepting API calls intended for a specific instance of another class. The example in Figure 4-3 shows that the Number class is intercepting a call to an instance of the String class. The result is that the l variable, an unsigned integer, is able to accept the assignment of the s variable as a number. That’s because the s variable is wrapped in a Number class, and is treated as a number.

Using the Decorator class, components are wrapped in decorators. The wrapping class is a concrete instance of a decorator, and the wrapped class is an instance of the concrete component class. Thus, the concrete component now “contains” the characteristics of the wrapper, but the characteristics are not inherited. Figure 4-4 shows an example.

Concrete decorator wrapping concrete component
Figure 4-4. Concrete decorator wrapping concrete component

The concrete component that’s wrapped by another class borrows characteristics of the wrapping class. When those characteristics are not needed, the instance of the concrete class is simply instantiated without being wrapped.

Flexibility and Adaptability

One good OOP practice is to create your classes so that they can be extended but not changed. If a class is changed, especially one with subclasses, you can quickly destroy an application. So the trick is to set up your classes so that it’s easy to extend them, yet keep them safe from alteration.

The Decorator design pattern allows you to do this. The model is grounded in a single class that is the superclass to all others. This core class is an abstract component class. An abstract decorator class is subclassed from this class, re-implementing the core methods. (In the context of ActionScript 3.0, the abstract nature of the class is simulated by using the override statement when re-implementing methods in subclasses.) All the other concrete component and decorator classes have the same root superclass, even though the decorator classes are subclassed from the abstract decorator class, and the component classes are directly from the abstract component class. So, when the concrete component objects are wrapped in concrete decorator objects, both objects share the same root superclass. Thus, the decorated objects can keep the core object unmodified while changing its responsibilities.

In a larger OOP framework, the Decorator pattern can add and subtract functionality from an object by wrapping selected components with selected decorators. Second, it can add more than a single functionality to an object by wrapping one wrapper inside another wrapper. As a result, the design pattern can change functionality without changing structure.

Minimalist Abstract Decorator

To get started with the Decorator design pattern, a minimum implementation needs the following:

  • An abstract component

  • An abstract decorator

  • A concrete component

  • Concrete decorators

For the sake of clarity, two decorators will be devised so that you can better see how the structure works (and it’s closer to the model shown in Figure 4-1.) Of all the classes devised in this pattern, by far the most important is the initial abstract class modeling the component. Every other class is subclassed from this initial class.

Abstract Component Class

To begin the process of creating a Decorator design pattern, create a component. You might think of a component as an undecorated Christmas tree, or even a person deciding what to wear. It’s simple because all of the added features are handled by decorators. Example 4-1 has only a single string variable that will be used to describe the component. Save the script as Component.as.

Example 4-1. Abstract Component class
package
{
    //Abstract Component in Decorator Design Pattern
    //**************
    //Abstract class
    public class Component
    {
        internal var information:String;

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

From this point on, all the classes will extend the Component class. Keeping in mind that the class is an abstract one—or at least is treated as one—its primary function is to establish a basic structure for the rest of the application. It contains a single variable, information, and a single getter function, getInformation(). These elements set up both the concrete components and decorations. Both components and decorations need to display information about their characteristics. A concrete Christmas tree displays information that lets you know that it’s a Christmas tree instead of another kind of object that can be decorated, such as a front yard to be decorated with gnomes and pink plastic flamingoes. Additionally, you want to be able to retrieve information, and so it has a getter function.

Abstract Decorator Class

Next, the abstract decorator is a subclass class of the Component class that inherits the information variable, so nothing is needed as far as the information variable is concerned. In fact, nothing’s required for this simple example other than defining the class as an extension of the Component class. However, the getInformation method is re-implemented independently of the Component class, using the override statement—which does what it says on the tin; it overrides parent class methods. This is done to distinguish the same method being used for the Decorator class from the method being used for the Component class. All the concrete decorations are subclassed from the Decorator class, and all concrete components are subclassed directly from the Component class. Further on, the concrete components will be wrapped in concrete decorators, and such distinctions become important in more complex implementations of the Decorator design pattern. The trace() statement is used to show you where in the process the abstract Decorator class appears. Save Example 4-2 as Decorator.as.

Example 4-2. Decorator.as
package
{
    //Abstract Decorator in Decorator Design Pattern
    //**************
    //Abstract class
    public class Decorator extends Component
    {
        trace("|*|Decorator|*|");
        override public function getInformation():String
        {
            return information;
        }
    }
}

Once the two abstract classes, Component and Decorator, have been established, it’s time to work with the concrete classes. For this example, only a single concrete component is created. Cleverly named ConcreteComponent, this class represents whatever will be decorated in a Decorator design pattern. You can have multiple concrete components that all use the same set of decorations, or only a single one. Later in this chapter, you will see an application where multiple concrete classes are decorated by a single set of decorators. The nice thing about the Decorator is that you can add as many concrete components as you want. Imagine a business web site where the concrete component represents an e-business site that you’ve worked on for several months. Using a Decorator design pattern, you’ve developed several useful and tested elements that are applied using decorators. Shortly after the project is complete, you get a request to develop another site with a different main product and an overlapping set of elements. Rather than starting from scratch, all you have to do is to add a different concrete component class and some new decorators that aren’t available in the original set.

Example 4-3 shows that the concrete component does little more than extend the Component class and add a constructor. It inherits all of the features of the Component class, and uses the information variable to place a message in the Output window to point to the decorators. Save the code in Example 4-3 as ConcreteComponent.as.

Example 4-3. ConcreteComponent.las
package
{
    //Concrete Component
    public class ConcreteComponent extends Component
    {
        public function ConcreteComponent()
        {
            //\u2794 is Unicode for a right-pointing arrow
            information = "Concrete Component is decorated with \u2794";
        }
    }
}

Note

In Example 4-3, you see that Unicode is inserted using the format \u + character code value. In the example, an arrow character is formed using Unicode 2794. To search for a character you may want to use, see http://www.fileformat.info/info/unicode/char/search.htm. You’ll find several different arrow characters you can use, including \u0363, \u2192, \u21aa, and \u21d2.

The next step is to build concrete decorator classes. In this abstract example, two concrete decorators will wrap themselves around the concrete component. So, to get started, we’ll need a Component instance variable to hold the component we’re wrapping.

var components:Component;

The variable is named components, with an “s,” because component is a built-in word in ActionScript 3.0. This variable is referenced in the decorator’s methods. Next, we need a way to affix the components variable to the object being wrapped. The following code shows how the component being wrapped is passed to the decorator’s constructor.

public function DecConA(components:Component)
{
    this.components=components;
}

Finally, you need a getter function to get the unique information from the concrete decorator. The public method inherited from the Decorator class must be set up using the override statement. Here, the method gets both the concrete component’s information [components.getInformation()], and adds on that of the concrete decoration [+ " Decoration Alpha:"]:

override public function getInformation():String
{
    return components.getInformation() + " Decoration Alpha:";
}

Concrete Decorations

Now, we’re all set to write the concrete decorator classes. Save Example 4-4 and Example 4-5 as DecConA.as and DecConB.as, respectively.

Note

In several of the decorator classes, you’ll see:

this.components = components;

Such code is a non-standard use of the this identifier, but it makes sense in this context because it differentiates the variable and parameter. Normally, you wouldn’t use the this identifier in this manner. The variable and parameter names are identical to emphasize the use of the abstract class in both cases, with one passing the value to the other.

Example 4-4. DecConA.as
package
{
    //Concrete Decorator "Alpha"
    public class DecConA extends Decorator
    {
        private var components:Component;
        public function DecConA(components:Component)
        {
            this.components=components;
        }
        override public function getInformation():String
        {
            return components.getInformation() + " Decoration Alpha:";
        }
    }
}
Example 4-5. DecConB.as
package
{
    //Concrete Decorator "Beta"
    public class DecConB extends Decorator
    {
        var components:Component;
        public function DecConB(components:Component) {
            this.components=components;
        }
        override public function getInformation():String
        {
            return components.getInformation() + " Decoration Beta:";
        }
    }
}

Wrapping Up

To execute the Decorator design pattern, the whole key lies in knowing how to wrap a component in a concrete decorator. First, you need to instantiate a concrete component.

var testComponent:Component = new ConcreteComponent();

Then, you wrap the component in one or more decorations using the following format:

componentInstance=new ConcreteDecorator(testComponent);

So, in our example, with two concrete decorations, we’d write:

testComponent=new DecConA(testComponent);
testComponent=new DecConB(testComponent);

At this point, testComponent is decorated with two decorations. We could duplicate the above lines adding the same two decorations as often as we wanted. Think of the decorations as red and green Christmas tree ornaments. The tree could be covered with nothing but red and green ornaments, rather than just one of each. The Decorator design pattern is employed cumulatively. That is, as you add each decoration, it’s added to those already wrapping the concrete component.

Finally, to see what havoc we’re wrought, we use the getter method—getInformation():

trace(testComponent.getInformation());

To see how all of this works, save Example 4-6 as DecTest.as in an ActionScript file. Then open a new Flash document file, and type DecTest in the Document class window in the Properties panel.

Example 4-6. DecTest.as
package
{
    import flash.display.Sprite;
    public class DecTest extends Sprite
    {
        public function DecTest()
        {
            //Instantiate Concrete Component
            var testComponent:Component = new ConcreteComponent();
            //Wrap first decorator around component
            testComponent=new DecConA(testComponent);
            //Wrap second decorator around component
            testComponent=new DecConB(testComponent);
            //Output results
            trace(testComponent.getInformation());
        }
    }
}

Figure 4-5 shows what you should see in your Output window when you test the movie.

Decorations on component
Figure 4-5. Decorations on component

To understand what’s going on in the Decorator pattern, go back and look at Figure 4-2. The example application first instantiated a ConcreteComponent() object. That object displays a message pointing to its decorations. Imagine that object (testComponent) as the smallest can to the far left in Figure 4-2. That can is then placed into decorator Can #1. At this point, the concrete component object (testComponent) is decorated with Can #1, but retains its original properties –much in the same way that a lawn decorated with a family of gnomes still retains its property of green grass. Next, Can #1, which now contains the concrete component, is dropped into Can #2. Now Can #2 has both Can #1 and the Concrete component Can. Thus, Can #2 has all of the properties of itself plus those of the cans inside.

In a sense, the whole process works like the compound operator, plus-equal (+=). Each decorator attaches itself to the existing component and its decorator. As each decorator is added, all the previous ones are retained but not duplicated, unless you add the same decorator more than once. So, as the output shows, you can add as many decorations as you want simply by wrapping them in the existing object in one of the decorators.

Applying a Simple Decorator Pattern in Flash: Paper Doll

You can move from a conceptual example to a concrete one with a computerized paper doll game. The paper doll component is the doll being dressed, and the decorations are clothing items selected for the doll. Using ActionScript’s new Loader class, different GIF files are used to decorate a base GIF image of a Victorian paper doll. As in the previous abstract example, this example uses a single concrete component and several decorations.

This concrete example does not have an interface—keeping the focus on the application’s use of a Decorator pattern. However, it does have a graphic output so you can see the pattern’s ultimate output. (Later in this chapter, there’s an example with a full interface.)

Setting Up the Component Class

The first class is the component class, Model. From this, all other classes are subclasses. It’s very simple but key to the success of the application. As you can see, it’s very close to the previous abstract Decorator. It consists of a method, getDressed(), and a string variable, whatToWear, as shown in Example 4-7. Save the file as Model.as.

Example 4-7. Model.as
package
{
    //Abstract class
    public class Model
    {
       protected var whatToWear:String;
        public function getDressed():String
        {
            return whatToWear;
        }
    }
}

Keep in mind that Example 4-7 should be treated as an abstract class. So the method getDressed() needs to be created as an override public function for it to work the way we want. However, the property whatToWear doesn’t need to be changed from its inherited characteristics.

Decorator Class Dressing the Dolls

Example 4-8 is the abstract Decorator class, Dresser, which extends the component class, Model. The major contribution here is simply re-implementing the getDressed() method with a reference to the inherited whatToWear property.

Example 4-8. Dresser.as
package
{
    //Abstract class
    public class Dresser extends Model
    {
        override public function getDressed():String
        {
            return whatToWear;
        }
    }
}

All of the concrete decorators will be subclassed for this class.

The Concrete Classes

Once the two main abstract classes in the Decorator pattern have been established, you’re all set to create the concrete ones. In the previous minimalist Decorator example, you saw the output using the trace() statement. Instead of trace() statements, both the concrete component and decorators need to be formatted for later parsing, so a tilde character (~) has been added as a demarcation point. Because all the strings from both the concrete component and decorators are grouped together into a single large string, the tilde serves as cutting point.

Concrete component class

The concrete component class is the only concrete class that extends directly from the abstract concrete class. All of the others in this application extend from the abstract decorator class. Using a simple constructor function, Sue(), the class assigns a value to the whatToWear variable. This is enough to identify the class as an instance of the main abstract component class, Model, and to establish a unique name. All decorations use the concrete component as the target for the decorations. Save Example 4-9 as Sue.as.

Example 4-9. Sue.as
package
{
    public class Sue extends Model
    {
        public function Sue()
        {
            whatToWear="~sue";
        }
    }
}

If you want to create more concrete component classes, all you need to do is create a similar class with a different name and value for the whatToWear variable. With this structure, you have no limit to the number of new concrete components you can add.

Concrete decorator classes

Moving from the minimalist example previously shown in this chapter, it’s a little easier to see how the Decorator pattern works by actually seeing something happening in a graphic display. When the initial instance of the concrete component is created, all references in the concrete decorator class are to that instance. (See Example 4-16, where the concrete component is wrapped in the decorators.) In all the concrete decorator classes, the reference to the model variable is a reference to the concrete component object. In this case, that’s the instance of the Sue() class, but it can be any instance of any concrete component. That’s why, if you wish to expand the application to include more concrete components (paper dolls to dress), you don’t have to make any fundamental changes. Just add another concrete component class. In Example 4-10 to Example 4-15, the captions are the filenames.

Example 4-10. OrangeDress.as
package
{
    public class OrangeDress extends Dresser
    {
        private var model:Model;
        public function OrangeDress(model:Model)
        {
            this.model=model;
        }
        override public function getDressed():String
        {
            return model.getDressed() + "~orangedress";
        }
    }
}
Example 4-11. BlueDress.as
package
{
    public class BlueDress extends Dresser
    {
        private var model:Model;
        public function BlueDress(model:Model)
        {
            this.model=model;
        }
        override public function getDressed():String
        {
            return model.getDressed() + "~bluedress";
        }
    }
}
Example 4-12. Bow.as
package
{
    public class Bow extends Dresser
    {
        private var model:Model;
        public function Bow(model:Model)
        {
            this.model=model;
        }
        override public function getDressed():String
        {
            return model.getDressed() + "~bow";
        }
    }
}
Example 4-13. Umbrella.as
package
{
    public class Umbrella extends Dresser
    {
        private var model:Model;
        public function Umbrella (model:Model)
        {
            this.model=model;
        }
        override public function getDressed():String
        {
            return model.getDressed() + "~ umbrella ";
        }
    }
}
Example 4-14. Hat.as
package
{
    public class Hat extends Dresser
    {
        private var model:Model;
        public function Hat(model:Model)
        {
            this.model=model;
        }
        override public function getDressed():String
        {
            return model.getDressed() + "~hat";
        }
    }
}
Example 4-15. Muff.as
package
{
    public class Muff extends Dresser
    {
        private var model:Model;
        public function Muff(model:Model)
        {
            this.model=model;
        }
        override public function getDressed():String
        {
            return model.getDressed() + "~muff";
        }
    }
}

All six decorations can be used in any combination you want with the instance of the Model class. This next section shows how to implement the Decorator pattern using the different decorators.

Implementing the Paper Doll Decorator

As noted, no interface was created for implementing this application, and so you can see clearly how the concrete component is wrapped by the decorators. Then, on the stage, you can see the effects of the different combinations.

The sequence is:

  1. Instantiate a concrete component.

  2. Wrap the concrete component in the desired decorator instance using the format:

    componentInstance = new DecoratorInstance(componentInstance)

  3. Apply the getter method (getDressed()) to the concrete component instance initiate to get the fully wrapped values. The values will include those of the concrete component instance and the applied decorations.

Example 4-16 illustrates how to implement the paper doll decorator.

Example 4-16. FashionShow.as
package
{
    import flash.display.Loader;
    import flash.net.URLRequest;
    import flash.display.Sprite;
    public class FashionShow extends Sprite
    {
        var ensemble:Array=new Array();
        public function FashionShow()
        {
            trace("||--Working--||");
            var doll:Model = new Sue();
            doll=new Hat(doll);
            doll=new OrangeDress(doll);
            //doll=new BlueDress(doll);
            //doll=new Umbrella(doll);
            doll=new Bow(doll);
            doll=new Muff(doll);
            var ready2wear:String=doll.getDressed();
            sewingMachine(ready2wear);
            for (x=0; x < ensemble.length; x++)
            {
                clothesOn(ensemble[x]);
            }
        }
        private function sewingMachine(wardrobe:String):Array
        {
            ensemble=wardrobe.split("~");
            ensemble.shift();
            return ensemble;
        }
        private function clothesOn(outfit:String)
        {
            var clothier:Loader=new Loader();
            var item:String="clothes/" + outfit + ".gif";
            var getItem:URLRequest=new URLRequest(item);
            clothier.load(getItem);
            this.addChild(clothier);
        }
    }
}

Once you’ve got everything ready to go, you’ll need some GIF files. The model (concrete component) should be an image of a doll that you will clothe with the Decorator pattern. Each filename needs to be the name of the value assigned to the whatToWear variable in the concrete component or decorator listing, minus the tilde (~) character. For example, the concrete component, (Sue), whatToWear variable is assigned the string value "~sue" and so the GIF filename for that component would be sue.gif. The decorator BlueDress has a whatToWear value of "~bluedress,” and so the image with the blue dress would be named bluedress.gif. Place all the GIF files, including the file representing the doll model, in a folder named clothes.

Finally, open up a new Flash document and set the stage dimensions to 300 × 650. In the Properties panel’s Document class window, type in FashionShow. The size and shape of your stage will depend on the size of the paper dolls you use.

Figure 4-6 shows two different combinations of decorations on the paper doll. The image on the left decorates with the hat, bow, orange dress, and muff, while the second is the blue dress, the bow and an umbrella. Each appears very different just by changing the combination of decorations.

Paper doll with two different decoration sets
Figure 4-6. Paper doll with two different decoration sets

Instead of paper dolls, any component could be decorated with any characteristics. Also, you can include more than a single property or method in a decoration. In the next section, we’ll take a look at adding additional properties with the Decorator design pattern.

Decorating with Deadly Sins and Heavenly Virtues

Action gaming pits different kinds of heroes and villains against one another, and the combatants have different weapons and shields. That is, they’re decorated with different characteristics and abilities. In order to see how to add some more functions to a Decorator pattern, what could be more appropriate than pitting good against evil?

Table 4-1 shows a list of deadly sins and heavenly virtues. (The list is considerably updated from Dante’s Inferno and Prudentius’ epic poem, Psychomachia, both of whom I understand were using Commodore-64’s to make their lists.)

Table 4-1. Decorations of good and evil

Deadly Sin

Description

Virtue

Description

Rage

Uncontrolled anger—striking out at syntax errors

Compassion

Caring about others—Helping procedural programmer transition to OOP

Malice

Meanness, malevolence, ill will, cruelty, and hatred toward others–unkind remarks about Linux.

Courage

Doing the right thing regardless of the danger—taking on object-oriented programming

Obfuscation

Hiding the truth— redefining an act, knowledge by adding confusion—coding without comments

Hope

Belief in eventual success of good over evil—you really can complete the project on time

Arrogance

Excessive pride, not considering others’ beliefs, feelings, or knowledge—belief that Microsoft Windows is the only real OS

Justice

A fair balance and even chance—using Windows, Mac OS and Linux

Prejudice

Judging others on the basis of stereotypes and not their actions—teasing Mac users

Openness

Capacity to consider new knowledge, ideas, and contrary ideas—writing a program on a Mac

Dogmatisms

Narrow, inflexible belief even in light of evidence to the contrary—continue to use procedural programming methods

Integrity

Maintaining values even when tempted to abandon them for short term gains—foregoing hacks even though they’d get the job done and the client would never know

Indifference

Seeing suffering and doing nothing or even caring to help—unwilling to offer help in learning OOP

Diligence

Willingness to stick with an especially difficult task to complete it—learning design patterns

Thinking about what has been presented so far in this chapter, the first thing that comes to mind is a property that describes each of the deadly sins and heavenly virtues. That’s easy enough, because just like the paper doll example, all we have to do is to assign a property value to each decorator. However, we can do more with the Decorator design pattern, as you’ll see in the next two sections.

Adding Properties and Methods

Up to now, we’ve used a single string variable with a single getter method for the basic abstract component class. However, like any other class, this basic structure can accommodate more than a single variable or function. Example 4-17 shows three variables and getter functions. Save the script as Component.as.

Example 4-17. Component.as
package
{
    //Abstract class
    public class Component
    {
        protected var soul:String="All that is inside a spirit";
        protected var goodness:Number;
        protected var vice:Number;

        public function getSoul():String
        {
            return soul;
        }
        public function good():Number
        {
            return goodness;
        }
        public function evil():Number
        {
            return vice;
        }
    }
}

Like the previous examples, we begin with a string property, soul. (It’s assigned a string literal, but that’s really not necessary because it’s an abstract class and will never be seen or used—just a clarification.) Next, two numeric properties are defined, goodness and vice. These two properties will collect all the accumulated good and evil in a soul.

Next, three getter functions are supplied to get the values of the string and two numeric variables. Now the abstract component class is all set to go.

Multiple Concrete Components

We could look at one soul at a time, but what fun is that? More important, in a lot of applications, having a single component to decorate isn’t too useful either. So instead of having a single soul to decorate, we’ll add two. For the time being, we’ll forego any debates about original sin and start our souls with a clean slate. Both goodness and vice will be set to zero. Just give them different soul values so they can be differentiated.

Example 4-18 and Example 4-19 provide the concrete components for Dick and Jane classes. Each class just needs a string value for soul and numeric values for goodness and vice—all inherited properties from the Component class. Save Example 4-18 as Dick.as, and Example 4-19 as Jane.as.

Example 4-18. Dick.as
package
{
    public class Dick extends Component
    {
        public function Dick()
        {
            soul = "Dick's soul\n";
            goodness=0;
            vice=0;
        }
    }
}
Example 4-19. Jane.as
package
{
    public class Jane extends Component
    {
        public function Jane()
        {
            soul = "Jane's soul\n";
            goodness=0;
            vice=0;
        }
    }
}

These concrete component classes are exactly like the previous examples we’ve examined, except we have two instead of one. Furthermore, instead of using a single string variable, two additional numeric variables are added, along with their assigned values. Otherwise, they’re just the same.

In these two concrete classes, the properties are inherited directly from the abstract Component class. No overrides or other special adjustments are needed. This is because the Dick and Jane classes are components, both to be decorated by the decorator classes. The overrides that generate unique methods and characteristics for the concrete decorators are accomplished by the decorator classes. Therefore, overrides by the concrete component classes are unnecessary.

Decorating with Multiple Properties

Multiple properties and methods are not difficult to add to components, and the same is true for decorator classes. Instead of a single property and method, you do essentially the same thing using multiple methods and classes. Example 4-20 shows the abstract Decorator class, subclassed from the Component class. Save the script as Decorator.as.

Example 4-20. Decorator.as
package
{
    //Abstract class
    public class Decorator extends Component
    {
        override public function getSoul():String
        {
            return soul;
        }
        override public function good():Number
        {
            return goodness;
        }
        override public function evil():Number
        {
            return vice;
        }
    }
}

As a subclass of the Component class, this Decorator abstract class does nothing more than re-implement the getter functions—one returning a string, and the other two returning a number. The properties that are to be returned were originally defined as properties in the Component class, and as a subclass of Component, the Decorator class doesn’t have to re-implement them. However, as you’ve seen in previous examples, the getter functions are re-implemented. The only difference is that there are more of them. However, the process and logic are the same.

Multiple Method Concrete Decorations

When it comes to the concrete decorations in this example application, we’re going to see something slightly new. First, take a look at Example 4-21. It’s a generic example and should not be placed in as actual code. Just look at it. Several actual concrete elements with working code will replace generic values.

Example 4-21. Generic concrete decoration
package
{
    //Generic—NOT implemented
    public class GoodEvil extends Decorator
    {
        private var components:Component;
        public function GoodEvil(components:Component)
         {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|GoodEvil";
        }
        override public function good():Number
        {
            return +/-00 + components.good();
        }
        override public function evil():Number
        {
            return +/-00 + components.evil();
        }
    }
}

If you break down Example 4-21, the first part looks exactly like the previous examples. A Component instance, components, is instantiated, and the constructor function wraps the components object in itself:

var components:Component;
public function GoodEvil(components:Component)
{
    this.components=components;
}

Next, the script re-implements the getter function, getSoul(), to return both the current value of the concrete component’s decorations plus its own decoration value. Again, this is what previous examples have done.

override public function getSoul():String
{
    return components.getSoul() + "|GoodEvil";
}

The next two functions add or subtract numeric values using the good() and evil() methods. Each good adds to a good and subtracts from an evil, and vice versa for a vice—adds to evil and subtracts from good. So depending on the concrete decorator, you add or subtract from each of the two return values, and add that to the current value of the concrete component.

override public function good():Number
{
    return +/-00 + components.good();
}
override public function evil():Number
{
    return +/-00 + components.evil();
}

Example 4-22 to Example 4-35 make up the seven deadly (revised) sins and seven heavenly (revised) virtues. However, what they really represent is the flexibility of the Decorator design pattern. (Also, they illustrate the complex issue of saving souls.)

The Good and Evil Concrete Decorators

Following are 14 concrete decorator classes. They’re all the same except for the names and values assigned to the numeric properties. It’s a lot easier just to do one, and then paste it into a new ActionScript file and edit in changes rather than doing them all from scratch. Once you’ve completed all 14, go ahead and add two more—good and evil concrete decorators of your own making. In Example 4-22 through Example 4-35, the filename is in the caption.

Heavenly Virtues

Example 4-22. Integrity.as
package
{
    public class Integrity extends Decorator
    {
        private var components:Component;
        public function Integrity(components:Component) {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Integrity";
        }
        override public function good():Number
        {
            return 14 + components.good();
        }
        override public function evil():Number
        {
            return −6 + components.evil();
        }
    }
}
Example 4-23. Hope.as
package
{
    public class Hope extends Decorator
    {
        private var components:Component;
        public function Hope(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Hope";
        }
        override public function good():Number
        {
            return 5 + components.good();
        }
        override public function evil():Number
        {
            return −10 + components.evil();
        }
    }
}
Example 4-24. Courage.as
package
{
    public class Courage extends Decorator
    {
        private var components:Component;
        public function Courage(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Courage";
        }
        override public function good():Number
        {
            return 10 + components.good();
        }
        override public function evil():Number
        {
            return −8 + components.evil();
        }
    }
}
Example 4-25. Compassion.as
package
{
    public class Compassion extends Decorator
    {
        private var components:Component;
        public function Compassion(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Compassion";
        }
        override public function good():Number
        {
            return 7 + components.good();
        }
        override public function evil():Number
        {
            return −15 + components.evil();
        }
    }
}
Example 4-26. Openness.as
package
{
    public class Openness extends Decorator
    {
        private var components:Component;
        public function Openness(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Openness";
        }
        override public function good():Number
        {
            return 12 + components.good();
        }
        override public function evil():Number
        {
            return −15 + components.evil();
        }
    }
}
Example 4-27. Diligence.as
package
{
    public class Diligence extends Decorator
    {
        private var components:Component;
        public function Diligence(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Diligence";
        }
        override public function good():Number
        {
            return 10 + components.good();
        }
        override public function evil():Number
        {
            return −5 + components.evil();
        }
    }
}
Example 4-28. Justice.as
package
{
    public class Justice extends Decorator
    {
        private var components:Component;
        public function Justice(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Justice";
        }
        override public function good():Number
        {
            return 9 + components.good();
        }
        override public function evil():Number
        {
            return −9 + components.evil();
        }
    }
}

Deadly Sins

Example 4-29. Rage.as
package
{
    public class Rage extends Decorator
    {
        private var components:Component;
        public function Rage(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Rage";
        }
        override public function good():Number
        {
            return −9 + components.good();
        }
        override public function evil():Number
        {
            return 8 + components.evil();
        }
    }
}
Example 4-30. Malice.as
package
{
    public class Malice extends Decorator
    {
        private var components:Component;
        public function Malice(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Malice";
        }
        override public function good():Number
        {
            return −14 + components.good();
        }
        override public function evil():Number
        {
            return 12 + components.evil();
        }
    }
}
Example 4-31. Prejudice.as
package
{
    public class Prejudice extends Decorator
    {
        private var components:Component;
        public function Prejudice(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Prejudice";
        }
        override public function good():Number
        {
            return −10 + components.good();
        }
        override public function evil():Number
        {
            return 15 + components.evil();
        }
    }
}
Example 4-32. Obfuscation.as
package
{
    public class Obsfuscation extends Decorator
    {
        private var components:Component;
        public function Obsfuscation(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Obsfuscation";
        }
        override public function good():Number
        {
            return −12 + components.good();
        }
        override public function evil():Number
        {
            return 7 + components.evil();
        }
    }
}
Example 4-33. Dogmatisms.as
package
{
    public class Dogmatisms extends Decorator
    {
        private var components:Component;
        public function Dogmatisms(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Dogmatisms";
        }
        override public function good():Number
        {
            return −12 + components.good();
        }
        override public function evil():Number
        {
            return 15 + components.evil();
        }
    }
}
Example 4-34. Arrogance.as
package
{
    public class Arrogance extends Decorator
    {
        private var components:Component;
        public function Arrogance(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Arrogance";
        }
        override public function good():Number
        {
            return −5 + components.good();
        }
        override public function evil():Number
        {
            return 5 + components.evil();
        }
    }
}
Example 4-35. Indifference.as
 package
{
    public class Indifference extends Decorator
    {
        private var components:Component;
        public function Indifference(components:Component)
        {
            this.components=components;
        }
        override public function getSoul():String
        {
            return components.getSoul() + "|Indifference";
        }
        override public function good():Number
        {
            return −9 + components.good();
        }
        override public function evil():Number
        {
            return 10 + components.evil();
        }
    }
}

At this point you can add your own concrete decorators. Use the same format as the others. You can also add additional concrete components. So instead of just Dick and Jane, you can add others you’d like to decorate with good and evil.

Implementing the Good and Evil Decorator

Instead of a single implementation, you can try out two different implementations. The first one dresses up the two different concrete components, and sends the results to the output window. The second takes the results, uses them to place movie clips on a “soul graph,” and adds a label to an angel or devil movie clip—depending on whether good or evil is predominant.

Dual implementation

The first implementation decorates both the Dick and Jane concrete components. This one is set up to use all 14 deadly sins and heavenly virtues, but you can use any combination you want. As each concrete component (life and light) is wrapped, the good and evil properties are incremented or decremented, depending on which decorator wraps the concrete component. The reference to the components object in each of the decorators is a reference to the concrete component being wrapped. With each new decorator, then, the value goes up and down depending on the wrapping decorator. Save Example 4-36 as MainDual.as in an ActionScript file. Open a new Flash document file, and save it as a Dual.fla. Type in MainDual in the Document class window in the Dual.fla Properties panel, and resave it as MainDual.as. Now test the movie.

Example 4-36. MainDual.as
package
{
    import flash.display.Sprite;
    public class MainDual extends Sprite
    {
        public function MainDual()
        {
            trace("||--Judging--||");
            var life:Component=new Dick();
            var light:Component=new Jane();
            //***Add Good
            life=new Courage(life);
            light=new Hope(light);
            light=new Compassion(light);
            life=new Openness(life);
            life=new Diligence(life);
            light=new Justice(light);
            //***Add Evil
            light=new Malice(light);
            life=new Prejudice(life);
            life=new Dogmatisms(life);
            life=new Arrogance(life);
            life=new Indifference(life);
            life=new Rage(life);
            light=new Obsfuscation(light);
            trace(life.getSoul()+"\nTotal Virture: "+
                 (life.good()-life.evil()));
            trace(light.getSoul()+"\nTotal Virture: "+
                 (light.good()-light.evil()));
        }
    }
}

The output for all of the good and evil together is:

||--Judging--||
Dick's soul
|Courage|Openness|Diligence|Prejudice|Dogmatisms|Arrogance
     |Indifference|Rage
Total Virture: −38
Jane's soul
|Hope|Compassion|Justice|Malice|Obsfuscation
Total Virture: 10

Because the trace() statement subtracts the evil() method from the good() method total value, any positive results indicate an abundance of good characteristics, and a negative result shows a plethora of negative traits.

You can “multi-wrap” using the same decorator more than once on the same concrete component. (Someone who thinks he can get the month-behind project done on time by not sleeping for a week is doubly hopeful, has a triple dose of diligence, and perhaps a double dose of arrogance.)

Charting souls

Like any of the other design patterns, what you do with the output is up to you. However, because ActionScript 3.0 is part of Flash, this next implementation shows how to place the output into different formats with graphic elements. The getSoul() generates a string, and the good() and evil() methods generate numbers. The string will be placed in a text field embedded in a movie clip, and the vertical position of the movie clip will be determined by the values generated by the good() and evil() methods. To get started, save the script in Example 4-37 in an ActionScript file named Soul.as.

Example 4-37. Soul.as
package
{
    import flash.display.Sprite;
    import flash.display.MovieClip;
    public class Soul extends Sprite
    {
        //Instantiate the two MovieClip objects in
        //the Library
        var devil:Devil=new Devil();
        var angel:Angel=new Angel();
        public function Soul()
        {
            var life:Component=new Jane();
            //***Add Good***
            life=new Courage(life);
            life=new Compassion(life);
            life=new Hope(life);
            //life=new Integrity(life);
            life=new Openness(life);
            life=new Diligence(life);
            life=new Justice(life);
            //***Add Evil***
            life=new Malice(life);
            life=new Prejudice(life);
            //life=new Dogmatisms(life);
            life=new Arrogance(life);
            //life=new Indifference(life);
            //life=new Rage(life);
            //life=new Obsfuscation(life);
            setAngelDevil(life.good(),life.evil(),life.getSoul());
        }
        private function setAngelDevil(right:Number,wrong:Number,
             eternalsoul:String)
        {
            this.addChild(devil);
            this.addChild(angel);
            var booWrong:Number=Number(wrong>0);
            var booRight:Number=Number(right>0);
            devil.x=330;
            devil.y=270-((wrong*booWrong)*(270/72));
            angel.x=96;
            angel.y=270-((right*booRight)*(270/60));
            if (booWrong)
            {
                devil.soul_txt.text=eternalsoul;
            } else
            {
                angel.soul_txt.text=eternalsoul;
            }
        }
    }
}

In looking at the script, all it does is pass the three different values generated by the getter methods, good(), evil() and getSoul(), to the setAngelDevil() function. The setAngelDevil() function uses two movie clips from the library and positions them on the stage. Depending on the outcome, the concrete component’s name appears in the angel or devil icon. Figure 4-7 shows what the output will look like. Use it as a guide for setting up your stage.

Decorator generated values used for placement and labels
Figure 4-7. Decorator generated values used for placement and labels

The following steps guide you through the process of preparing the two MovieClip classes in the Flash IDE and setting the stage as a “soul chart.”

  1. Open a new Flash document file, type in Soul in the Document class window, and save the file as SoulChart.fla.

  2. Add a layer to the existing layer, and name the top layer, “Labels” and the bottom layer “Lines.” (Say that last sentence fast three times!)

  3. Select Insert → New Symbol from the menu bar. Type in “Angel” in the Name window, and click the Export for ActionScript checkbox. Click OK. You are now in the Symbol Edit Mode. (Be sure to capitalize the “A” in “Angel” because this is a class name.)

  4. In the Symbol Edit window, draw or import an image of an angel.

  5. Click the Text icon in the Tools panel, and select Dynamic Text for the type of text. In the Properties panel, provide the instance name soul_txt for the dynamic text object.

  6. Exit the Symbol Edit Mode by clicking the Scene 1 icon. You should now see a movie clip icon in the Library named “Angel.”

  7. Repeat steps 3 to 6, substituting “Devil” for “Angel.” Once you’re finished, you should see both Devil and Angel movie clip icons in the Library panel.

  8. Click the Lines layer and add 11 horizontal lines and one vertical line as shown in Figure 4-7. Lock the layer.

  9. Click the Labels layer, number the lines from 0 to 100, and place a “Good” and “Evil” label at the top of the stage as shown in Figure 4-7. Lock the layer and save the file once again.

By adding and removing the comment lines in the Soul.as file, you can change the vertical positions of the angel and devil images. You may have to make some adjustments depending on the size of your angel and devil movie clips and/or if you change the default size of the stage from 550 by 400.

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.

Summary

The Decorator design pattern excels in adding features to core objects without having to fundamentally change those objects. Like the ornaments on a Christmas tree, the ornaments change the appearance of the tree, but the tree itself is not changed at all. Anything from the appearance of a web site to the contents of an online shopping cart can be structured using a Decorator pattern.

The Decorator should be considered a core design pattern when your project has to be updated with the addition of new objects and new features for those objects. We like to think of the Decorator as a “100-year” pattern. Imagining concrete components as types of people and decorations as the clothes they wear, it’s not too difficult to envision a web site being updated with different kinds of people spanning a century—from a blacksmith at the beginning of the 20th century to a nanotechnologist at the beginning of the 21st century. Likewise, all kinds of people can be styled with a range of clothing over the same time span, from a blacksmith’s leather apron in 1900 to an astronaut’s spacesuit in 2000. However, the programmer who is thinking ahead from one century to the next is able to use a Decorator pattern, and doesn’t have to change the core component at all. He just adds more concrete components and decorators as needed.

At the same time that the Decorator pattern is open to adding new features without changing the structure of the program, both components and decorators can be deleted when they’re no longer needed. Further, changes to existing components and decorators are easy to make without altering anything other than their specifics, such as a string’s label or a number’s value.

The Decorator pattern, though, has certain drawbacks. For example, in this chapter, some readers may have been thinking that they could have programmed the same exact functionality for some of the sample applications using a fraction of the code and far fewer classes. That’s definitely true, and for specific applications, the Decorator design pattern may be like swatting a fly with an elephant gun. However, like all design patterns, the developer has to be judicious in selecting which, if any pattern, he wants to employ. That decision needs to be made not just on the current size of the application, though. When starting any project where the Decorator pattern is considered, you have to ask yourself: are the concrete components and decorators going to grow and change or are they going to be fairly static? So, even though your application may begin as an acorn, you have to envision the oak tree as a possibility and plan accordingly.

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