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

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.

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