Object-Oriented Programming, Design Patterns, and ActionScript 3.0: Chapter 1

by William Sanders, Chandima Cumaranatunge
ActionScript 3.0 Design Patterns book cover

This excerpt is from ActionScript 3.0 Design Patterns. Now that ActionScript is reengineered from top to bottom as a true object-oriented programming (OOP) language, reusable design patterns are an ideal way to solve common problems in Flash and Flex applications. If you're an experienced Flash or Flex developer ready to tackle sophisticated programming techniques with ActionScript 3.0, this hands-on introduction to design patterns is the book you need. ActionScript 3.0 Design Patterns takes you step by step through the process, first by explaining how design patterns provide a clear road map for structuring code that actually makes OOP languages easier to learn and use. You then learn about various types of design patterns and construct small abstract examples before trying your hand at building full-fledged working applications outlined in the book.

buy button

Chapter 1. Object-Oriented Programming, Design Patterns, and ActionScript 3.0

Let it be your constant method to look into the design of people's actions, and see what they would be at, as often as it is practicable; and to make this custom the more significant, practice it first upon yourself.

--Marcus Aurelius

The life history of the individual is first and foremost an accommodation to the patterns and standards traditionally handed down in his community.

< --Ruth Benedict

At the lowest cognitive level, they are processes of experiencing, or, to speak more generally, processes of intuiting that grasp the object in the original.

--Edmund Husserl

The Pleasure of Doing Something Well

The idea of design patterns is to take a set of patterns and solve recurrent problems. At the same time (even in the same breath), the patterns reflect good object-oriented programming (OOP) practices. So, we cannot separate OOP from design patterns, nor would we want to do so.

In answering the question of why bother with design patterns, we are really dealing with the question of why bother with OOP. The standard response to both design patterns and OOP often points to working with a team of programmers and speaking the same language. Further, it's easier to deal with the complexities involved with programming tasks requiring a division of labor for a large project using an object metaphor and practices.

In addition to coordinating large projects, programmers use both OOP and design patterns to deal with change. One key, important element, of design patterns is that they make changing a program much easier. The bigger a program and the more time you've spent developing it, the greater the consequences in making a change to that program. Like pulling a string in a sweater that unravels it, changing code in a program can have the same unraveling consequences. Design patterns and good OOP ease the task of making changes in complex programs, and reduce the changes or global problems.

Team coordination and application update and maintenance are reasons enough to learn design patterns. However, this is not the case if most programs you write are relatively short, you don't work with teams, and you don't worry about change. Then rewriting the short program is neither time-consuming nor difficult. What possible reason would you then have for learning design patterns?

Beside the fact that ActionScript 3.0 is based on ECMAScript and is not likely to have major changes with each new release of Flash or Flex as in the past, you have a far more personal reason for learning design patterns. Alexander Nakhimovsky and Tom Myers, in writing about using OOP with JavaScript (Wrox, 1998), point out the value in the pleasure derived from doing something well. Like any endeavor, whether it be skateboarding or building a house, people can find pleasure in doing a task well. By "doing something well," we do not mean an obsessive perfectionism—especially since perfectionism often leads to task paralysis. Rather, like any task that one can see an outcome and experience a process of accomplishment, when it's done right, you experience the craftsman's pleasure of the creative process and results.

Sequential and Procedural Programming

If you've never heard of sequential programming, that's the kind of programming you've most likely been doing. Most amateur programmers just write one statement after another, and any program that has the correct sequence of statements works just fine. However, as programs became more complex, programmers ran into an unruly jumble of code often called spaghetti programs. To remedy the jumble effect of sequential programming, programmers began organizing programs into a set of procedures and set of rules, and procedural programming was born. Instead of willy-nilly GOTO statements jumping all over a program, subroutines modularly defined program flow with appropriate GOSUB/RETURN procedures to keep everything tidy.

Note

The RETURN statements back then were different from what they are today. A RETURN meant to return to the position in a sequence of code where the GOSUB had originated. In ActionScript, a return statement means that an operation sends back information generated in the operation [the method or procedure].

Also, from procedural programming came the concept of scope so that variables in functions and subroutines could be reused and one procedure would not contaminate another.

The great majority of programming languages today are considered procedural in that they have the concepts and syntax that support it. The different versions of BASIC are procedural, as are languages like ColdFusion, PHP and Perl. However, C++ is a procedural language, as is ECMAScript (ActionScript 3.0) and Ada, languages many consider object-oriented. Languages like Java are considered true OOP languages. Without going into a lot of detail, the reason Java is considered a true OOP language and the others are not is because the only kind of procedure in Java is a class method. Its structure forces procedures to be class methods, and doesn't allow other procedures to operate outside the class structure.

Note

You might be surprised at how heated a discussion can get when it comes to a language being an OOP language or not. Two versions of OOP criteria exist. One is fairly inclusive and allows any language with certain features that can generate OOP code to be considered OOP. (ActionScript 3.0 is among those.) The other version has a restrictive criterion that includes those languages that only allow methods as procedures to be admitted to the exclusive club of OOP languages. Both versions have valid points. However, we will sidestep the issue by not taking a position, but note that both sides agree that you can create good OOP code with a procedural language.

To make a long story short, this does not mean that the other languages are unable to generate true OOP programs. Well before Java was even available, developers were creating OOP programs. Some languages, especially those with the ability to use class structures and override methods, such as ActionScript 3.0, are more OOP friendly than others. As ActionScript has matured from a few statements to a true ECMAScript language, it has become more OOP friendly.

Transition to OOP

Changing from sequential or procedural programming to OOP programming is more than picking up a language that gives you little choice in the matter, as is the case with Java. However, certain changes in a language can make it more amenable to OOP, even if it's not considered a true OOP language by some criterion. In the following sections, some new features in Flash CS3 provide a summary of relevant changes to the way ActionScript is used.

MovieClip and Button scripts

For the ActionScript veterans whose introduction to programming was writing little sequential scripts or procedures using the on statements associated with MovieClip or Button objects, you're probably aware that the latest version of Flash doesn't allow script embedded in either.

Note

Built-in State Machines

While most programmers welcomed the demise of movie clip and button embedded scripts, one astute programmer observed that Flash used to have built-in state machines. Jonathan Kaye, PhD, co-author of Flash MX for Interactive Simulation: How to Construct and Use Device Simulations (Delmar Learning, 2002), noted that the button and movie clip scripts often served to create state machines. Each button or movie clip could serve as an encapsulated, context-sensitive trigger for changing state. (See how design patterns deal with State Machine in Chapter 10.)

In general, the demise of movie clip and button scripts is seen as a boon to better programming, especially OOP programming. Keeping track of the isolated button and movie clip codes could be a headache, even with relatively small applications. For structuring larger programs, where OOP and Design Patterns are most useful, having movie clips and buttons floating around with their own code moves the problem from the realm of headache to nightmare. So, for learning design patterns, be glad you don't even have to think about little scripts isolated in movie clips and buttons.

Timeline scripts

Another kind of scripting you'll be seeing less of in Flash are those embedded in your Timeline. For the most part, placing scripts in the Timeline probably left a lot to be desired in the first place, but worked out to be a convenient location. In ActionScript 2.0, you were able to place a script in a class and call it from a script embedded in the Timeline, and so all that the Timeline code was really used for was to call a class that would launch a script in an ActionScript file (.as). That being the case, the Flash CS3 .fla file has a little window where you can add the name of the class to call. (See the next section.) So, if all you want to do is to call a program and compile it into an SWF file, you no longer need to use the Timeline for your code at all.

However, Flash CS3 doesn't do away with code in the Timeline. You can still use it, but in this book, we use it selectively only with movie clips that are called from a class outside the movie clip or button class. (See the section "Movie Clip and Button Classes.")

Document class

You won't be placing much, if any, code in the Timeline using ActionScript 3.0. Rather than using an object with a Timeline script, you can now compile your .as files by entering the name of the class name you want to launch your application. Figure 1.1, “Document class window” shows how to use the Document class window in the Properties panel to enter the name of the class you want to launch:

Figure 1.1. Document class window

Document class window


You can still use the Timeline, but unless there's a good reason to do so, there's no need. Most of the examples in this book use the Sprite object instead of the MovieClip class. A Sprite object has no Timeline, but a MovieClip class does. So using Sprite objects save a bit of extra weight that the Timeline has.

Movie clip and button classes

In Flash CS3, MovieClip and Button objects you create using the Symbol dialog box and store in the Library can be made available to work with ActionScript 3.0. Unlike ActionScript 2.0 where MovieClip and Button symbols could be associated with a class, with Flash CS3, they can be made into classes themselves. The object's name entered into the Name window when the symbols are created becomes the class name for the object. (In past versions, references to a movie clip are made through an instance name. You can still make those references can still be made, but in a different context.)

The advantage of this new procedure is that the symbol objects can be instantiated just like any other class through the code, as long as the symbols are in the Library. You don't have to place them on the stage. They can be dynamically instantiated and placed into a display array just like a dynamically generated object. Further, objects contained within the MovieClip or Button can be addressed as a property just like any other class.

While this book is in no way an introduction to Flash CS3, walking through one example of this new way of creating a class with movie clips and buttons may be useful to those new to Flash and experienced users alike. The following steps walk you through this new feature:

  1. Open a new Flash document and save it as rocket.fla.

  2. Select Insert→New Symbol from the menu bar to open the Create New Symbol Dialog box. Enter "Rocket" in the Name window, and Click OK to enter the Symbol Edit Mode.

  3. In the Symbol Edit Mode, draw a rocket on the stage with the dimensions W=89, H=14, as shown in Figure 1.2, “Rocket drawing”. Once finished, position the drawing at X=0, Y=0. Click the Scene 1 icon to exit the Symbol Edit Mode.

    Figure 1.2. Rocket drawing

    Rocket drawing

  4. Select Insert→New Symbol from the menu bar to open the Create New Symbol Dialog box. Enter "FireRocket" in the Name window, select Movie clip as Type, and click the Export for ActionScript checkbox. Once you've clicked the checkbox, Figure 1.3, “Setting a MovieClip class” shows what the dialog box looks like. Notice that the Base class is flash.display.MovieClip. The base class is the reference to the package required for ActionScript to display a MovieClip object. Click OK to enter the Symbol Edit Mode

    Figure 1.3. Setting a MovieClip class

    Setting a MovieClip class

  5. Drag a copy of the Rocket movie clip from the Library to the center of the stage. Move the center point of the movie clip to the rear of the rocket and position it at X=0, Y=0.

  6. Click on Frame 40 of the Timeline and press F5 to create 40 frames. Click Frame 40 again and press F6 to insert a keyframe. Click on the keyframe in frame 40 and move the rocket to X=400, Y=0.

  7. Click on the first keyframe, and, in the tween drop-down menu in the Properties inspector, select Motion. You should now see a blue arrow in the Timeline. Move the playhead from left to right to make sure that the motion tween is working right. Figure 1.4, “Rocket in motion tween” shows what you should see:

    Figure 1.4. Rocket in motion tween

    Rocket in motion tween


  8. Open the Actions panel. Click on a blank area of the stage to make sure you don't have any objects selected, and then click on Frame 1. In the Actions panel, type in the stop() statement. Save the Rocket.fla file.

  9. Open a new ActionScript file and save it as TestRocket.as in the same folder as the Rocket.fla file. Enter the script in Example 1.1, “TestRocket.as” in the TestRocket.as file, and save the file once again:

    Example 1.1. TestRocket.as

    package
    {
        import flash.display.Sprite;
    
        public class TestRocket extends Sprite
        {
            private var fireRocket:FireRocket;
            public function TestRocket()
            {
                fireRocket=new FireRocket();
                fireRocket.x=50;
                fireRocket.y=100;
                addChild(fireRocket);
                fireRocket.gotoAndPlay(2);
            }
        }
    }


  10. Finally, open the Rocket.fla file, and in the Document class window in the Properties panel, type in TestRocket and save the file. Then test the movie by pressing Ctrl + Enter (Command + Return on the Mac). You should see the rocket move from left to right across the screen and then return to its original position.

Using Flash in a more or less traditional manner to create movie clips is still an important part of using ActionScript, but it has changed. You can no longer attach a class to a movie clip as was the case in previous versions. However, in creating applications using design patterns, you can still integrate different aspects created in the Flash IDE. So while ActionScript 3.0 has made the leap to a true ECMAScript language, it has not abandoned its roots in animated graphics.OOP Basics

If you're familiar with OOP and have been practicing good OOP for some time now, you might want to skip this section or just skim over it to see if we've added anything new, or if there's something new as far as ActionScript is concerned. Later in this chapter, we introduce good practices in OOP on which design patterns are based. These more advanced concepts depend on understanding these basics. However, this short discussion is no substitute for a more in-depth understanding of OOP. If this is your first exposure to OOP, you will definitely want to supplement your understanding of OOP with an introductory book dedicated to OOP.

Throughout the book, you will find references to how a design pattern employs different basic and design pattern OOP principles. Each chapter includes a section on key OOP concepts, and so what you read in this introductory chapter is only the first of many times an OOP concept will be described. This is intentional. By looking at an OOP concept from different angles, we believe you will have a better understanding of OOP's many nuances. We ourselves were surprised at how different design patterns brought out different perspectives on the same OOP concept and helped further clarify it.

To get started, we'll review the four basic OOP concepts:

  • Abstraction

  • Encapsulation

  • Inheritance

  • Polymorphism

Each of these concepts needs reflection, and if you're new to OOP, don't worry about getting it right the first time. We go over these concepts time and again in the design pattern chapters.

Abstraction

In general, an abstraction is a model or ideal. You don't have all of the details, but you have the general parameters that can be filled in with details. Further, an abstraction is clear enough for you to tell one abstraction from another. Take, for example, two jobs your company is trying to fill. One's for a Web designer and the other's for a programmer. To advertise for the position, you would not describe the person as a specific person but instead in terms of the characteristics you want for the position. You might have the two abstractions representing the two different positions:

Two Positions Open:

  • Programmer

  • Experienced with multi-programmer projects

  • Experienced with middleware and database programming

  • ECMAScript programming background

  • OOP and Design Pattern programming skills

  • Web designer

  • Experienced with creating Web graphics

  • Familiar with animation graphics

  • Can work with vector graphics

  • Client-centered approach

You can tell the difference between the two positions and their general requirements (properties), but the details are left fairly open. A programmer is unlikely to apply for the Web designer position and a designer is just as unlikely to apply for the programmer position. However, a pool of applicants could have a wide range of skills that would provide the concrete details for each position. For example, one programmer may have PHP middleware skills and/or MySQL database skills, while another may be experienced in using ASP.NET, C# and MS SQL. The abstraction is in the job description and the details are provided by the applicants' unique sets of skills and experience.

In Object-Oriented Design with Applications (Benjamin/Cummings), Grady Booch, one of the design pattern pioneers, provides the following definition of an abstraction that is both clear and succinct:

An abstraction denotes the essential characteristics of an object that distinguish it from all other kinds of object and thus provide crisply defined conceptual boundaries, relative to the perspective of the viewer.

Booch's definition pretty well describes the two job descriptions. The descriptions provide the essential characteristics of the position and they distinguish one from the other.

Abstractions in ActionScript 3.0

Turning now to abstractions in ActionScript programming, we'll take a simple video player for an example. This player will be made of certain elements that we need; so we start by listing them as abstractions:

  • A Net connection

  • A video screen

  • A stream

  • An FLV file to play

If we put these together just right, we'll be able to play a video. However, instead of starting with an abstraction, we want to start with something concrete that works for us right away. Enter the code in Example 1.2, “PlayVideo.as” saving the file using the name in the caption:

Note

Throughout the book, with a few exceptions, caption names represent the name used for the file.

Example 1.2. PlayVideo.as

package
{
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import flash.media.Video;
    import flash.display.Sprite;

    public class PlayVideo extends Sprite
    {
        public function PlayVideo()
        {
            var nc:NetConnection=new NetConnection();
            nc.connect(null);
            var ns:NetStream = new NetStream(nc);
            var vid:Video=new Video();
            vid.attachNetStream(ns);
            ns.play("adp.flv");
            addChild(vid);
            vid.x=100;
            vid.y=50;
        }
    }
}

You'll need an FLV file named adp.flv—any FLV file with that name will work. Open a new Flash document file, enter PlayVideo in the Document class window, and test it.

To change this to an abstract file, take out all specific references to any values with the exception of the null value in the NetConnection.connect() method. (We could pass that value as a string, but we're leaving it to keep things simple.) Example 1.3, “PlayVideoAbstract.as” shows essentially the same application abstracted to a "description" of what it requires to work.

Example 1.3. PlayVideoAbstract.as

package
{
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import flash.media.Video;
    import flash.display.Sprite;

    public class PlayVideoAbstract extends Sprite
    {
     public function PlayVideoAbstract(nc:NetConnection,
           ns:NetStream,vid:Video,flick:String,xpos:uint,ypos:uint)
        {
            nc=new NetConnection();
            nc.connect(null);
            ns= new NetStream(nc);
            vid=new Video();
            vid.attachNetStream(ns);
            ns.play(flick);
            vid.x=xpos;
            vid.y=ypos;
            addChild(vid);
        }
    }
}

All the values for the different elements (with the exception of null) have been abstracted to describe the object. However, like a job description that abstracts requirements, so too does the PlayVideoAbstract class. All the particulars have been placed into one long set of parameters:

PlayVideoAbstract(nc:NetConnection,ns:NetStream,vid:Video,flick:String,
     xpos:uint,ypos:uint)

The abstract parameters in the constructor function let us add any concrete elements we want, including the specific name of a video we want to play. Example 1.4, “PlayAbstract.as” shows how concrete instances are implemented from an abstract class:

Example 1.4. PlayAbstract.as

package
{
    import flash.display.Sprite
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import flash.media.Video;

    public class PlayAbstract extends Sprite
    {
        private var conn:NetConnection;
        private var stream:NetStream;
        private var vid:Video;
        private var flick:String="adp.flv";
        public function PlayAbstract()
        {
var playIt:PlayVideoAbstract=new PlayVideoAbstract(conn,stream,vid,
    flick,100,50);
            addChild(playIt);
        }
    }
}


All the entire class does is to create a single instance of the PlayVideoAbstract class and place it on the stage. Private variables serve to provide most of the concrete values for the required parameters. Literals provide the data for both the horizontal (x) and vertical (y) positions of the video. To test it, just change the Document class name in the Flash document (FLA) file to PlayAbstract.

Why Abstractions Are Important

We can see two key reasons that abstractions are important for both OOP and Design Patterns. Rather than being dogged by minutiae of the problem, abstraction helps to focus on what parts, independent of their details, are required to solve the problem. Does this mean that you ignore the details? Not at all. Rather, the details are handled by adding them just when they're needed. For instance, in the example in the previous section, the exact video file is unimportant. All that's important is that some video name (a detail) be provided when we're ready to play it. We don't need to build a theater around a single movie. Likewise, we don't need to build a class around a single video file.

The second advantage of abstraction is flexibility. If you're thinking that in the previous section the Example 1.2, “PlayVideo.as” was easier and took less code and classes, you're right. However, suppose you want to place four videos on the stage. Then, all you would need to do is to create four instances using the abstract class instead of re-writing three more classes. In other words, the second method using abstraction is more flexible. In addition to adding more videos instances, we can easily change the video file we choose to play.

Encapsulation

Encapsulation is what makes a code object an object. If you have a tail, four legs, a cold nose and a bark, you do not have a dog. You just have a collection of parts that make up a dog. When you bring all of the doggy parts together, you know that each part is a part but collectively, you do not think of parts but a reality sui generis. That is, a dog is an object unto itself and not doggy parts that happen to hang together. Encapsulation has a similar effect on a collection of operations and properties.

Encapsulation has been used synonymously with other terms such as component and module. In the context of OOP, encapsulation is often called a black box, meaning you can see it do certain things but you cannot see the inner workings. Actually, a lot of things we deal with all the time are black boxes, such as our dog. We can see the dog do a lot of different things, and we can interact with the dog. However, we really don't know (or usually care) about how the physiology of the dog works—dogs are not transparent. They're black boxes.

The good thing about the concept of a black box is that we don't have to worry about the inner workings or parts. We just have to know how we can deal with it, secure in the knowledge that whatever makes the black box work is fine as long as it works as we think it should.

Hiding Your Data from Bad Consequences

To see why you might want to encapsulate your data, we'll take a look at two programs. One is not encapsulated, leading to unwanted consequences, and the other is encapsulated, preventing strange results.

If you make a dog object, you may want to include an operation that includes the way a dog communicates. For purposes of illustration, we'll include a method called dogTalk that will let the dog make different sounds. The dog's communication will include the following:

  • Woof

  • Whine

  • Howl

  • Grrrr

We'll start off with bad OOP to illustrate how you may end up with something you don't want in your dog's vocabulary. Example 1.5, “NoEncap.as” is not encapsulated and will potentially embarrass your dog object:

Example 1.5. NoEncap.as

package
{
    //This is BAD OOP -- No encapsulation
    import flash.text.TextField;
    import flash.display.Sprite;

    public class NoEncap extends Sprite
    {
        public var dogTalk:String="Woof, woof!";
        public var textFld:TextField=new TextField();

        public function NoEncap()
        {
            addChild(textFld);
            textFld.x=100;
            textFld.y=100;
        }
        function showDogTalk()
        {
            textFld.text=dogTalk;
        }
    }
}


As a black box, you should not be able to change the internal workings of a class, but this class is wide open, as you will see. You can interact with an encapsulated object through its interface, but you should not allow an implementation to make any changes it wants. Example 1.6, “TestNoEncap.as” breaks into the object and changes it in ways you don't want:

Example 1.6. TestNoEncap.as

package
{
    import flash.display.Sprite;

    public class TestNoEncap extends Sprite
    {
        public var noEncap:NoEncap;
        public function TestNoEncap()
        {
            noEncap=new NoEncap();
            noEncap.dogTalk="Meow";
            noEncap.showDogTalk();
            addChild(noEncap);
        }
    }
}


Open a new Flash document file, and, in the Document class window, type in TestNoEncap. When you test the file, you'll see "Meow" appear on the screen. Such a response from your dog object is all wrong. Dogs don't meow and cats don't bark. However, that's what can happen when you don't encapsulate your class. When you multiply that by every un-encapsulated class you use, you can imagine the mess you might have. So let's find a fix for this.

Private variables

The easiest way to insure encapsulation is to use private variables. The private statement in ActionScript 3.0, whether it's used with variables, constants or methods (functions) makes sure that only the class that defines or declares it can use it. This not only shuts out implementations that attempt to assign any value they want, but it also excludes subclasses. (This is a difference from ActionScript 2.0; so watch out for it if you're converting an application from ActionScript 2.0 to ActionScript 3.0.)

To see how the private statement will change how the application works, Example 1.7, “Encap.as” changes the NoEncap class by adding the private statement to the variables:

Example 1.7. Encap.as

package
{
    //This is GOOD OOP -- It has encapsulation
    import flash.text.TextField;
    import flash.display.Sprite;

    public class Encap extends Sprite
    {
        private var dogTalk:String="Woof, woof!";
        private var textFld:TextField=new TextField();

        public function Encap()
        {
            addChild(textFld);
            textFld.x=100;
            textFld.y=100;
        }
        function showDogTalk()
        {
            textFld.text=dogTalk;
        }
    }
}


Also, minor changes have to be made to the test file. The supertype implemented must be changed. Example 1.8, “TestEncap.as” shows the new test class, TestEncap , for the Encap class.

Example 1.8. TestEncap.as

package
{
    import flash.display.Sprite;

    public class TestEncap extends Sprite
    {
        public var encap:Encap
        public function TestEncap()
        {
            encap=new Encap();
            encap.dogTalk="Meow";
            encap.showDogTalk();
            addChild(encap);
        }
    }
}

Go ahead and test it by changing the Document class name to TestEncap. This time, though, you'll get the following error in the Complier Errors panel:

Line 11: 1178: Attempted access of inaccessible property dogTalk through
 a reference with static type Encap.
Source: encap.dogTalk="Meow";

It shows the source of the error to be the line:

encap.dogTalk="Meow";

That error reflects the fact that it attempted to access a private variable outside the class. To fix the script, comment out the offending line:

//encap.dogTalk="Meow";

Try testing it again. This second time, everything works fine, except, you don't get the dog object expressing "Meow." You see "Woof, woof."

You may be thinking that private variables really limit what you can do. Suppose you want the dog object to howl, growl or whimper? How do you make the changes dynamically? Preventing an encapsulated object from doing something wrong is one thing, but how can an object be set up to accept variables?

The many meanings of interface

In this book, you will find the term interface used in different contexts, and each context gives the term a slightly different meaning. (Thanks a lot!) Up to this point, you're probably familiar with terms like UI (user interface) or GUI (graphic user interface). These terms refer to different tools you use to interact with a program. For example, a button is a common UI in Flash. When you click a button, something predictable happens. You may not know how it happens (or care), but you know that if you press the button, the video will play, a different page will appear, or an animation will start. So if you understand the basic concept of a UI, you should be able to understand how an interface works with an object.

With a UI, the black box is the application you're using, whether it's shopping at eBay or using a word processor. If you follow certain rules and use the different UIs in the appropriate manner, you get what you want. In the same way, an encapsulated object is a black box, and the interface describes the ways you can interact with it programmatically. It's the UI for the object.

Design Patterns: Elements of Reusable Object-Oriented Software (page 13) nicely clarifies object interfaces and their signatures. An object's signature is its operation name, parameters and return datatype. Figure 1.5, “Object's signature” graphically shows the makeup of a typical object's signature.

Figure 1.5. Object's signature

Object's signature

All of an object's signatures defined by its operations is the interface. In this context then, the interface for the object constitutes the rules for access, list of services, and controls for it.

Note

Wait! There's more! Later in this chapter you will find an interface statement as part of ActionScript 3.0's lexicon, which is a whole different use of the term, interface. You will also find the term used synonymously with supertype elsewhere in this book. All of the different uses of interface will be explained in time.

Before getting bogged down in contextual definitions, it's time to move on to see what an encapsulated object's interface looks like. The following section does just that.

Getters and setters

The most common way to enforce encapsulation but to give implementations access to an object is with getter and setter interfaces. The object controls both access to it and what is allowed. Keeping our example of a dog object, we know that the dog has a limited vocabulary, and it's not one that includes "Meow." So, we'll just make a setter that allows only the dog's limited vocabulary.

A setter method includes parameter variables of some kind, and an algorithm that allows certain things and excludes others. However, most setters just assign the parameter value to a private member regardless of its value. The algorithm and everything else in the function that makes up the method is invisible to the implementation, and if the wrong parameter or wrong datatype is entered, either an error message appears or the value will not be passed. The following shows the general structure of a setter:

function setterMethod(parameter)
{
    if(parameter=="OK")
    {
        private variable = parameter;
    }
}

So the trick is to have the implementation pass any data to the object as a parameter, and if the parameter is acceptable, the private variable is assigned the parameter's value. This is a very general overview of the setter, but it shows the essentials of how it works.

Compared to setters, getter methods are pretty straightforward. In Example 1.7, “Encap.as”, the showDogTalk() method is the getter function. The getter method's job is to provide data for the implementation. Thus, while the original example doesn't have a setter method, it does have a getter. The setter makes sure that the client gets only what it's supposed to get.

In Example 1.9, “EncapSet.as”, the private variable, dogTalk, is not assigned a default value. However, the variable is still used in both the setter and getter methods. As you will see when you test the new class, EncapSet, the implementation has access to the private variable through the setter's interface.

Example 1.9. EncapSet.as

package
{
    //This is BETTER OOP -- It's got encapsulation
    //plus a decent interface for an object

    import flash.text.TextField;
    import flash.display.Sprite;

    public class EncapSet extends Sprite
    {
        private var dogTalk:String;
        private var textFld:TextField=new TextField();

        public function EncapSet()
        {
            addChild(textFld);
            textFld.x=100;
            textFld.y=100;
        }

        //Setter
        function setDogTalk(bowWow:String)
        {
            switch (bowWow)
            {
                case "Woof" :
                    dogTalk=bowWow;
                    break;

                case "Whine" :
                    dogTalk=bowWow;
                    break;

                case "Grrrr" :
                    dogTalk=bowWow;
                    break;

                case "Howl" :
                    dogTalk=bowWow;
                    break;

                default :
                    dogTalk="Not dog talk!";
            }
        }

        //Rendering value
        function showDogTalk()
        {
            textFld.text=dogTalk;
        }
    }
}

As you can see in Example 1.9, “EncapSet.as”, the setter method allows only the four expressions we had listed for a dog. Anything else (including "Meow") is not allowed. Next, Example 1.10, “TestEncapSet.as” shows how to interface with the encapsulated dog object:

Example 1.10. TestEncapSet.as

package
{
    import flash.display.Sprite;

    public class TestEncapSet extends Sprite
    {
        private var encapSet:EncapSet
        public function TestEncapSet()
        {
            encapSet=new EncapSet();
            encapSet.setDogTalk("Howl");
            encapSet.showDogTalk();
            addChild(encapSet);
        }
    }
}


Enter TestEncapSet in the Document class of the FLA file and test it. As you will see, the string "Howl" is perfectly acceptable. Now, test it again using "Meow." This time, you will see that the object rejected the kitty cat sound as "Not dog talk." This arrangement represents the best of both worlds; encapsulation and a way to execute operations within an encapsulated object.

The get and set methods

Another way to maintain encapsulation and hide the information in your objects is to use the ActionScript 3.0 get and set methods. Some programmers find it awkward to create their own getter and setter methods as we did in the previous section, preferring the simplicity of the get accessor and set mutator.

Note

Accessors and mutators versus Frankenstein: they sound like something from a horror flick, but the terms accessor and mutator are used to describe getters and setters. The accessor (getter) does access or get information, and that's perfectly reasonable. But mutators? Well, if we think about it, a mutation does refer to a change, and when we set information, we do indeed change it. It too makes perfect sense. So if you're happily reading an article on design patterns and you see the terms accessor or mutator, don't let it creep you out.

In looking at how get and set are used, a key feature is the absence of parentheses. Changes (setting) are not accomplished by adding values. Rather, the getters and setters are treated like properties where values are assigned or retrieved through assignment.

Example 1.11, “FlowerShop.as” and Example 1.12, “Send Flowers.as” show how you can lock up your encapsulated objects using getters and setters with the get and set methods.

Example 1.11. FlowerShop.as

package
{
    public class FlowerShop
    {
        private var buds:String;

        public function FlowerShop():void {}

        //Getter function
        public function get flowers():String
        {
            return buds;
        }

        //Setter function
        public function set flowers(floral:String):void
        {
            buds=floral;
        }
    }
}

In Example 1.12, “Send Flowers.as”, keep in mind that flowers is a method, and not a property. However, setting and getting values using the flowers() method looks exactly like setting and getting a property value.

Example 1.12. Send Flowers.as

package
{
    import flash.display.Sprite;

    public class SendFlowers extends Sprite
    {
        public function SendFlowers()
        {
            var trueLove:FlowerShop = new FlowerShop();
            //Set values
            trueLove.flowers="A dozen roses";
            //Get values
            trace(trueLove.flowers);
            //Set different values
            trueLove.flowers="And a dozen more....";
            //Get the changed values
            trace(trueLove.flowers);
        }
    }
}

Using Encapsulation and Design Patterns

This section on encapsulation has been fairly long. The reason for the attention to encapsulation is because of its importance to good OOP; it's a crucial element in design patterns. Of the 24 original design patterns, only 4 have a class scope and the remaining 20 have an object scope. Rather than relying on inheritance (which is discussed in the next section), the great majority of design patterns rely on composition.

Later in this chapter, in the section, "Favor Composition," you will see how design patterns are made up of several different objects. For the design patterns to work the way they are intended, object encapsulation is essential.

Inheritance

The third key concept in good OOP is inheritance. Inheritance refers to the way one class inherits the properties, methods and events of another class. If Class A has Methods X, Y, and Z, and Class B is a subclass (extends) Class A; it too will have Methods X, Y and Z. This saves a lot of time and adds functionality to your programming projects. If you've done virtually any programming using ActionScript 3.0, you've probably extended the Sprite class as we've done in Example 1.1, “TestRocket.as” through Example 1.10, “TestEncapSet.as”. Because of inheritance, the new classes (subclasses) derived from the Sprite class have all of the functionality of the Sprite class, in addition to anything you add to the subclass.

Looking at the Ancestors

The best place to start looking at how inheritance works is with ActionScript 3.0. Open your online ActionScript 3.0 Language Reference. In the Packages window, click flash.display. In the main window that opens the Package flash.display information, click MovieClip in the Classes table. At the very top of the Class MovieClip page, you will see the Inheritance path:

MovieClip→Sprite→DisplayObjectContainer→InteractiveObject→
     DisplayObject→ EventDispatcher→Object

That means that the MovieClip class inherited all of the characteristics from the root class, Object, all the way to Sprite object and everything in between.

Scroll down to the Public Properties section. You will see nine properties. Click on the Show Inherited Public Properties link. Now you should see 43 additional properties! So of the 52 properties in the MovieClip class, you can see that only 9 are unique to MovieClip class. The rest are all inherited. Likewise, the methods and properties we added are unique to the class—the rest are inherited from Sprite.

To see the effect of inheritance and the effect of using one class or another, change the two references to Sprite to MovieClip in Example 1.9, “EncapSet.as”. Because the MovieClip class inherits everything in the Sprite class, the application should still work. As you will see, it works just fine. The reason that Sprite is used instead of MovieClip is that we did not want to have any unnecessary baggage—just the minimum we needed. If you change Sprite to the next class it inherits from, DisplayObjectContainer, you will see that the application fails. This means that the application requires one of the Sprite class properties that is not inherited.

Note

One byte over the line: in Example 1.9, “EncapSet.as”, if you substitute the MovieClip for Sprite classes for the parent class, you will find that your SWF file is larger than when you tested it with Sprite (708 bytes versus 679 bytes). The 29 byte difference probably won't bloat your program significantly, but with added classes in design pattern applications, an unnecessary byte here and one there might add up. (When you win a contract because your application was 1 byte less than the competition's, you'll be glad we had this little chat.)

In addition to seeing what is inherited in ActionScript 3.0 in the Language Reference, you might also want to note what's in the packages you import. If you import flash.display.* you can bring in everything in the display package. That's why importing just what you need, such as flash.display.Sprite or flash.display.Shape, is far more frugal and less cluttering.

Writing Classes for Inheritance

As can be seen from looking at the inheritance structure of ActionScript 3.0, a well-planned application benefits greatly from a well-organized plan of inheritance. To see how inheritance works from the inside, the next example provides a simple inheritance model for our four-legged friends. Even with this simple example, you can see what is inherited and what is unique to an application.

Example 1.13, “QuadPets.as” through Example 1.16, “TestPets.as” make up the application illustrating inheritance. The first class, QuadPets, is the parent or superclass with a constructor that indicates when an instance of the class is instantiated using a trace statement. Any class that inherits the QuadPets class gets all of its methods and interfaces.

Example 1.13. QuadPets.as

package
{
    public class QuadPets
    {
        public function QuadPets():void
        {
            trace("QuadPets is instantiated");
        }
        public function makeSound():void
        {
            trace("Superclass:Pet Sound");
        }
    }
}

Any class that uses the extends statement to declare a class inherits the class characteristics it extends. The class that's extended is the superclass (or parent). The class that extends another class is the subclass. Both the Dog and Cat classes are subclasses of the QuadPets class. They inherit all of the superclass' functionality. To see how that happens, we'll have to first create the two subclasses.

Example 1.14. Dog.as

package
{
    public class Dog extends QuadPets
    {
        public function Dog():void
        {
        }
        public function bark():void
        {
            trace("Dog class: Bow wow");
        }
    }
}

Example 1.15. Cat.as

package
{
    public class Cat extends QuadPets
    {
        public function Cat():void
        {
        }
        public function meow():void
        {
            trace("Cat class: Meow");
        }
    }
}

To see how the Dog and Cat classes inherit the operations of the superclass, the TestPets class simply invokes the single method (makeSound) from the superclass. As you can see from the Dog and Cat classes, no such method can be seen in their construction, and so you can see that it must have originated in the QuadPets class.

Example 1.16. TestPets.as

package
{
    import flash.display.Sprite;

    public class TestPets extends Sprite
    {
        public function TestPets():void
        {
            var dog:Dog=new Dog();
            dog.makeSound();
            dog.bark();
            var cat:Cat=new Cat();
            cat.makeSound();
            cat.meow();
        }
    }
}

In addition to invoking the makeSound () method, the Dog and Cat instances invoke their own methods, bark() and meow(). Also, when you test the application, you will see:

QuadPets is instantiated

That output is caused by the line:,

trace("QuadPets is instantiated");

placed in the constructor function of the QuadPets class. It fires whenever an instance of the class is invoked. So in addition to having the capacity to use methods from the superclass, subclasses inherit any actions in the constructor function of the superclass.

Open a Flash document, and type TestPets in the Document class window. When you test it, you should see the following in the Output window:

QuadPets is instantiated
Superclass:Pet Sound
Dog class: Bow wow
QuadPets is instantiated
Superclass:Pet Sound
Cat class: Meow

Looking at the output, both the dog and cat instances display two superclass messages (QuadPets is instantiated, Superclass:Pet Sound) and one message unique to the respective subclasses (Dog class: Bow wow, Cat class: Meow.) These examples show how inheritance works, but in practical applications, and in design patterns, inheritance is planned so that they cut down on redundancy and help build a program to achieve an overall goal.

Using Interfaces and Abstract Classes in ActionScript 3.0

Inheritance can also be linked to two other structures; interfaces and abstract classes. However, the connection between the interface structure (a statement in ActionScript 3.0) or the abstract class and inheritance is a bit different from the one with the class structure we've examined thus far.

Interface constructs

First of all, in this context, interface refers to an ActionScript 3.0 statement and not the object interface discussed in the "Encapsulation and Design Patterns" section. While a class can be said to be an abstraction of an object, an interface is an abstraction of methods. They are widely used in design patterns. Beginning in Chapter 5 with the adapter pattern, you will see interfaces at work in several of the other design patterns.

To begin to see what an interface does, a simple example illustrates the use of one. However, once you start seeing how they're used in design patterns, you will better see their utility. Example 1.17, “BandFace.as” shows how a typical interface is created. The application is made up of Example 1.17, “BandFace.as” to Example 1.20, “MakeSound.as”.

The first thing we'll do is to make our interface. As you can see in Example 1.17, “BandFace.as”, the single function is quite simple and devoid of content. It does have a name, parameter, and return type, but note that the function is only a single line.

Example 1.17. BandFace.as

package
{
    //Interface
    public interface BandFace
    {
        function playInstrument(strum:String):void;
    }
}

Each implementation of the interface must have exactly the same structure in all of the methods in the interface, and if anything is not the same, you'll get an error message. As long as the signature for the methods is the same, everything should work fine. Example 1.18, “Guitar.as” is the first implementation of the of the BandFace interface.

Example 1.18. Guitar.as

package
{
    public class Guitar implements BandFace
    {
        public function Guitar() {}

        public function playInstrument(strum:String):void
        {
            trace("Playing my air "+ strum);
        }
    }
}

Looking at Example 1.19, “Bongo.as” and the Bongo class, at first you may think that the method is built incorrectly. It's wholly different from the Guitar class in its details, but the method's signature is identical.

Example 1.19. Bongo.as

package
{
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.net.URLRequest;

    public class Bongo implements BandFace
    {

        public function Bongo(){}

        private var sound:Sound;
        private var playNow:SoundChannel;
        private var doPlay:URLRequest;

        public function playInstrument(strum:String):void
        {
            sound=new Sound();

            doPlay=new URLRequest(strum);
            sound.load(doPlay);
            playNow=sound.play();
        }
    }
}

Remember, when working with interfaces, the number of methods in an interface can be many or few, but as long as each implementation of the interface includes every method in the interface and maintains the structure, everything works fine.

You may be wondering where the inheritance is. Given the fact that you must build all of the interface's methods, it looks more like a customization than an inheritance. However, the BandFace subclasses all inherit its interfaces. So essentially, the subclasses inherit the interface but not the implementation.

Finally, to test the application, the MakeSound class, listed in Example 1.20, “MakeSound.as”, tests both classes and their very different constructions of the single method from the BandFace interface. You'll need an MP3 file named bongo.mp3 (use any handy MP3 file) saved in the same folder as the MakeSound.as file.

Example 1.20. MakeSound.as

package
{
    import flash.display.Sprite;
    public class MakeSound extends Sprite
    {
        private var guitar:BandFace;
        private var bongo:BandFace;

        public function MakeSound():void
        {
            guitar=new Guitar();
            guitar.playInstrument("Gibson");

            bongo=new Bongo();
            bongo.playInstrument("bongo.mp3");
        }
    }
}

Note that both instances, guitar and bongo, are typed to the supertype, BandFace, and not to either the Guitar or the Bongo classes. This practice follows the first principle of reusable object-oriented design: Program to an interface, not an implementation.

The purpose of doing so is to maintain flexibility and reusability. This principle will be fully explored elsewhere in this chapter and in Chapter 8, but for now, just note that fact.

Note

A word about the interface and abstract class naming conventions used in this book: with the focal point of this book on object-oriented programming and design patterns, we use naming conventions that best reflect and clarify the structure of the different design patterns. As a result, we don't always follow some of the naming conventions. One convention is to name interfaces beginning with a capital I. So following that convention, the BandFace interface would have been named IBandFace. Where using the I does not interfere with clarifying a design pattern structure, we use it, but where we have several interfaces in a design pattern, often we forego that convention. Another convention is to name abstract classes using Abstract+ Something. So, AbstractHorses would be a name for a class you'd otherwise name Horses. Again, our focus on revealing structure supersedes using these conventions. We differentiate abstract from concrete classes using comments in the code. Throughout the book, however, we attempt to be as clear as possible in naming the different classes. You may want to adopt some of these more common conventions to aid in keeping your code clear once you better understand them.

Abstract classes and overriding inheritance

As you become familiar with design patterns, you'll see more and more use of interfaces and its close cousin the abstract class. In ActionScript 3.0 the abstract class is a little problematic because no class can be actually defined as abstract. While you can use the public statement to make a class public, ActionScript 3.0 (and ECMAScript) chose not to include abstract classes in the language, as does Java.

However, you can create an abstract class in ActionScript 3.0. All you have to do is create a regular class and treat it as an abstract class. Like interfaces, abstract classes can have abstract methods that are not directly implemented. Rather, abstract classes are subclassed and any abstract methods are overridden and implemented very much like methods are in using interfaces. However, abstract classes can have implemented methods as well; so when you need both abstract and implemented methods, abstract classes are key to such design patterns as the Factory Method (Chapter 2), Decorator (Chapter 4) and the Composite (Chapter 6) as well as others.

You know from inheritance that when one class subclasses another, the subclass inherits the methods of the superclass. With an abstract class, you do not implement the class but instead subclass it, and then implement the subclass and its methods. Because of the abstract nature of at least some of the methods in the abstract class, you must override them. Using the override statement, an overridden class maintains its signature but can have its own unique details. You must be sure that the name, number, type of parameters, and the return type are the same. In other words, when you override a method from an abstract class, you treat the method exactly the same as an interface method.

Example 1.21, “AbstractClass.as” through Example 1.23, “ImplementSub.as” make up an application that illustrates how an abstract class works. The abstract class has two methods; a concrete one and an abstract one.

Example 1.21. AbstractClass.as

package
{
    //Abstract class
    public class AbstractClass
    {
        function abstractMethod():void {}
        function concreteMethod():void
        {
            trace("I'm a concrete method from an abstract class")
        }
    }
}

The subclass inherits the methods and interface from the abstract class, and it provides details for the abstract method by overriding it. However, the subclass leaves the concrete class as is and does not attempt to instantiate the superclass.

Example 1.22. Subclass.as

package
{
    //Subclass of Abstract class
    public class Subclass extends AbstractClass
    {
        override function abstractMethod():void
        {
            trace("This is the overidden abstract method");
        }
    }
}

When the application finally implements the methods originating in the abstract class, it does so by programming to the interface (AbstractClass) but instantiates through the subclass (Subclass). So the instance, doDemo, is typed as AbstractClass but instantiated as Subclass.

Example 1.23. ImplementSub.as

package
{
    //Implement Subclass of Abstract class
    import flash.display.Sprite;

    public class ImplementSub extends Sprite
    {
        private var doDemo:AbstractClass;

        public function ImplementSub()
        {
            doDemo=new Subclass();
            doDemo.abstractMethod();
            doDemo.concreteMethod();
        }
    }
}

The following shows what appears in the Output window when you test the program:

This is the overidden abstract method
I'm a concrete method from an abstract class

At this point you may be scratching your head wondering why you should go through this kind of convolution to do what you could do with a non-abstract class. Instead of subclassing the abstract class, overriding one of the methods, and then implementing the subclass, we could have just written both methods the way we wanted them in the first place and not have to override anything. The next section attempts to answer that question.

Why use interfaces and abstract classes?

To understand why to use interfaces and abstract classes, we need to consider the whole purpose of design patterns. It's the ability to reuse object-oriented software. We've been using fairly simple examples to help clarify the concepts. However, typical software is usually far more complex, and the algorithms more sophisticated. Once you complete a project, you're likely to have to make a change. The larger and more complex the project, the more difficult it is to reuse the assets you've developed, maintain the interconnections and generally make any kind of change without unraveling the whole thing or introducing code that may make future change impossible.

To illustrate what this means, consider the application in Example 1.21, “AbstractClass.as” to Example 1.23, “ImplementSub.as” in the previous section. Multiply the complexity of the class AbstractClass by a factor of 10. Do the same to the number of subclasses. Now you're dealing with some serious complexity. Next, consider that you need to maintain the functionality of the class, Subclass, and yet change the abstract method in the AbstractClass for a new functionality. Also, you have to maintain all of the interfaces and connections you've built.

Because you used an abstract class, you can create a new subclass that overrides the abstract function and uses it in a different way. To see how this all works, we'll make a new subclass of the AbstractClass and change the abstract method. We're not changing anything else in the entire application, so we don't have to worry about everything working together because we can separate our new subclass and method and only implement them where needed. Other subclasses of the AbstractClass are unaffected. Example 1.24, “SubclassChange.as” and Example 1.25, “ImplementSubChange.as” show the two new classes created to make the change.

Example 1.24. SubclassChange.as

package
{
    //A New Subclass of Abstract class with a change
    public class SubclassChange extends AbstractClass
    {
        override function abstractMethod():void
        {
            trace("This is the new abstractMethod!!")
            trace("Made just one little important change.");
            trace("But this still works just fine!");
        }
    }
}

Note that instead of having a single trace statement, Example 1.24, “SubclassChange.as” uses three. This modification simulates a more complex change. However, Example 1.25, “ImplementSubChange.as”, which implements the application, is identical to Example 1.23, “ImplementSub.as”.

Example 1.25. ImplementSubChange.as

package
{
    //ImplementSubChange of Abstract class
    import flash.display.Sprite;

    public class ImplementSubChange extends Sprite
    {
        private var doDemo:AbstractClass;

        public function ImplementSubChange()
        {
            doDemo=new SubclassChange();
            doDemo.abstractMethod();
            doDemo.concreteMethod();
        }
    }
}

All the changes are revealed in the line,

doDemo.abstractMethod();

Even though the line is the same as Example 1.23, “ImplementSub.as”, the output window reveals that a good deal has changed :

This is the new abstractMethod!!
Made just one little important change.
But this still works just fine!
I'm a concrete method from an abstract class

What's important is not that it made the changes. The point that you need to consider carefully is the fact that such changes can be made without disrupting the other elements in your application that you have reused. So without having to rewrite the entire application, you can keep its functionality while making necessary changes.

Polymorphism

The root of polymorphism is a word that could easily replace it to describe the process— metamorphosis. The term is from the Greek metamorphoun, meaning to transform. One definition describes metamorphosis as a magic-like transformation—something a sorcerer would do. If you like, think of polymorphism as giving the programmer the power of a sorcerer.

Generating Polymorphism Using An Abstract Class

Another definition of polymorphism is that it allows for many (poly) forms (morph). In Example 1.21, “AbstractClass.as” through Example 1.25, “ImplementSubChange.as”, you saw how the abstractMethod had more than a single form. That's polymorphism. To see it in a more practical application, consider people's taste in music and emerging forms of music. Suppose you create a class with a method set up to show a person's musical tastes, and then build your application using that method for the different genres. Example 1.26, “Polymorphism.as” through Example 1.31, “PlayMusic.as” provide a simple example of such an application. The root abstract class is named Polymorphism in honor of the concept it illustrates.

Example 1.26. Polymorphism.as

package
{
    //Abstract class
    public class Polymorphism
    {
        public function myMusic():void
        {
            //Reserve details for subclasses
        }
    }
}

Example 1.27. Rock.as

package
{
    public class Rock extends Polymorphism
    {
        override public function myMusic():void
        {
            trace("Play Jimmie");
        }
    }
}

Example 1.28. Classic.as

package
{
    public class Classic extends Polymorphism
    {
        override public function myMusic():void
        {
            trace("Play Mozart");
        }
    }
}

Example 1.29. Country.as

package
{
    public class Country extends Polymorphism
    {
        override public function myMusic():void
        {
            trace("Play Willie");
        }
    }
}

Example 1.30. Jazz.as

package
{
    public class Jazz extends Polymorphism
    {
        override public function myMusic():void
        {
            trace("Play Coltrane");
        }
    }
}

Example 1.31. PlayMusic.as

package
{
    import flash.display.Sprite;
    public class PlayMusic extends Sprite
    {
        var rock:Polymorphism;
        var classic:Polymorphism;
        var country:Polymorphism;
        var jazz:Polymorphism;

        public function PlayMusic():void
        {
            rock=new Rock();
            rock.myMusic();
            classic=new Classic();
            classic.myMusic();
            country=new Country();
            country.myMusic();
            jazz=new Jazz();
            jazz.myMusic();
        }
    }
}

When you test the program, you'll see the following:

Play Jimmie
Play Mozart
Play Willie
Play Coltrane

As you can see, it's not rocket science and hardly cloaked in mystery. It's just polymorphism. All instances were typed to the abstract class Polymorphism. Then they were instantiated using the subclasses, and each launched the same method, myMusic(). However, with each usage, even though all shared the same datatype (or supertype), Polymorphism, each instance's use of the method generates a unique outcome.

Looking at the program and your MP3 library, you may be thinking, "That's not nearly enough music categories." What about R&B, Country, Alternative, Hip-Hop, and Cowboy music? (Cowboy?) We couldn't agree more. Go ahead and create new subclasses using polymorphism to add all the categories you want. (This is not one of those exercises where the answer is at the end of the chapter or the back of the book. You're on your own. Programmers don't get polymorphism right without writing their own code. See if you've got the right stuff.) By the way, notice that you can make all the changes you want with polymorphism without having to change any of the other classes. That's the whole point of polymorphism.

Implementing Polymorphism with Interfaces

The ActionScript 3.0 interface statement and structure is the other powerful tool for polymorphism in both object-oriented programming and design patterns. Suppose you're building an e-business site. You have no idea how many new products the site will have, but you know the site will have many different products that change regularly. You're going to need something that will handle a wide variety of products, and a way of displaying them.

You know that you'll need the following operations, but you're not sure about the details:

  • Description

  • Price

  • Product display

One way to set up an e-business site would be to create a class that has methods for all three operations. However, suppose you find out that the client wants different kinds of displays for the different products. She wants dynamically loaded video for video products (e.g. TV sets), sound for sound products (e.g. MP3 players) and graphic images for all other products. The operations for both description and price methods are pretty standard, but the display method is going to have to be very flexible. That's where polymorphism comes in. We could use an abstract class, but to provide ever more flexibility just in case other unanticipated requirements crop up, we'll use an interface.

Example 1.32, “IBiz.as” through Example 1.36, “DoBusiness.as” make up the e-business application. The interface, IBiz, contains the primary operations with unique signatures but not any details. This will allow us to create the unique details we need as long as we keep all the signatures the same.

Example 1.32. IBiz.as

package
{
    public interface IBiz
    {
        function productDescribe():String;
        function productPrice(price:Number):String;
        function productDisplay(product:String):void;
    }
}

Example 1.33, “Plasma.as” introduces something new. The Plasma class extends one class, Sprite, and implements another, IBiz. Placing a video on the stage requires a Sprite object, but we still need the IBiz interface methods; so using both the extends and implements statements, we're able to have the best of both worlds.

Example 1.33. Plasma.as

package
{
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import flash.media.Video;
    import flash.display.Sprite;

    public class Plasma extends Sprite implements IBiz
    {
        private var ns:NetStream;
        private var vid:Video;
        private var priceNow:Number;


        public function productDescribe():String
        {
            return "42 inch TV with Plasma screen";
        }

        public function productPrice(price:Number):String
        {
            priceNow=price;
            return "$" + priceNow + "\n";
        }

        public function productDisplay(flv:String):void
        {
            var nc:NetConnection=new NetConnection();
            nc.connect(null);
            ns=new NetStream(nc);
            ns.play(flv);
            vid=new Video();
            vid.attachNetStream(ns);
            addChild(vid);
        }
    }
}

In comparing Example 1.33, “Plasma.as” with Example 1.34, “MP3Player.as”, both the productDescribe() and productPrice() methods look pretty much the same other than the literal used in the return statement in the productDescribe(). In fact, by adding a string parameter to the productDescribe() method, we could make them identical. However, the point of this application is to demonstrate polymorphism, and so creating different forms of the methods is intentional.

You can see the practicality of polymorphism by comparing the productDisplay() methods in the two examples. Example 1.33, “Plasma.as” is set up to play a video and Example 1.34, “MP3Player.as” a sound. However, note that the identical signatures are maintained in both examples.

Example 1.34. MP3Player.as

package
{
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.net.URLRequest;

    public class MP3Player implements IBiz
    {
        private var sound:Sound;
        private var playChannel:SoundChannel;
        private var doPlay:URLRequest;
        private var MP3Price:Number;

        public function productDescribe():String
        {
            return "MP3Player with 1 Terabyte of Memory";
        }

        public function productPrice(price:Number):String
        {
            MP3Price=price;
            return "$" + MP3Price + "\n";
        }

        public function productDisplay(song:String):void
        {
            sound=new Sound();
            playChannel=new SoundChannel();
            doPlay=new URLRequest(song);
            sound.load(doPlay);
            playChannel=sound.play();
        }
    }
}

Example 1.35, “Computers.as” has further differences in its productDisplay() details. Instead of either video or sound operations, it contains operations for loading files. Yet, like the other two, the signature and interfaces are still the same.

Example 1.35. Computers.as

package
{
    import flash.display.Loader;
    import flash.net.URLRequest;
    import flash.display.Sprite;

    public class Computers extends Sprite implements IBiz
    {
        private var hotz:Number;
        private var loadPix:Loader;
        private var namePix:URLRequest;

        public function productDescribe():String
        {
            return "New 10 gHz processor, 30 gigabyte RAM, includes
                32 inch screen";
        }

        public function productPrice(price:Number):String
        {
            hotz=price;
            return "$" + hotz + "\n";
        }

        public function productDisplay(computer:String):void
        {
            loadPix=new Loader();
            namePix=new URLRequest(computer);
            loadPix.load(namePix);
            addChild(loadPix);
        }
    }
}

Now that all the classes and the interface are complete, all that's left is a class to test the application. As you've seen above, good OOP and preparation for working with design patterns suggests programming to the interface and not the implementation as we've been doing. However, in looking at Example 1.36, “DoBusiness.as”, all of the typing (setting the datatype) is to the Plasma, MP3Player, and Computers implementations and not the IBiz interface. This is because each of the implementations extended the Sprite class. So we're dealing with two supertypes, IBiz and Sprite. To test the application, the instances are typed as one of the specific implementations instead of the interface. In the section "Program to Interfaces over Implementations" later in this chapter, you'll see how to construct your interface and implementation so that you can still type to the interface when there's more than a single type in the implementation. (In Example 1.20, “MakeSound.as” you can see how an interface structure is programmed to the interface instead of an implementation.)

Example 1.36. DoBusiness.as

package
{
    import flash.display.Sprite;

    public class DoBusiness extends Sprite
    {
        public function DoBusiness()
        {
            //TV
            var tv:Plasma=new Plasma();
            trace(tv.productDescribe());
            trace(tv.productPrice(855));
            tv.productDisplay("plasma.flv");
            tv.x=160;
            tv.y=110;
            addChild(tv);

            //MP3
            var mp3:MP3Player=new MP3Player();
            trace(mp3.productDescribe());
            trace(mp3.productPrice(245));
            mp3.productDisplay("bongo.mp3");

            //Computers
            var computers:Computers=new Computers();
            trace(computers.productDescribe());
            trace(computers.productPrice(1200));
            computers.productDisplay("whizbang.gif");
            addChild(computers);
        }
    }
}

You will need an FLV file, an MP3 file and a GIF file for this application. Name the FLV file plasma.flv, the MP3 file bongo.mp3, and the GIF file whizbang.gif. The contents are unimportant but the names and file types are important.

Open a new Flash document, save it in the same folder with the rest of the application, and type in DoBusiness in the Document class window. When you test it, the Output window shows the following:

42 inch TV with Plasma screen
$855

MP3Player with 1 Terabyte of Memory
$245

New 10 gHz processor, 30 gigabyte RAM, includes 32 inch flat screen monitor
$1200

In addition to messages in the Output window, you should see something like the following on the stage:

Figure 1.6. Graphic file and video appear on the stage

Graphic file and video appear on the stage


In addition to the visible graphics, you should also hear the sound played by the MP3 file. (This multimedia experience has been brought to you by polymorphism and the letter P.)

Principles of Design Pattern Development

The founding principles of design patterns are laid down in the Gang of Four's (GoF) canon, Design Patterns: Elements of Reusable Object-Oriented Software. The two essential principles are:

  • Program to an interface, not an implementation

  • Favor object composition over class inheritance

If you skipped the section on the four basic OOP principles because you already know them, the first principle of Design Pattern OOP was introduced, but only briefly. Not to worry, we'll go over it in detail.

Before going further, we need to clarify some basic terms. While these terms are fairly simple, they can trip you up if you don't understand how they're used in context. The subsequent chapters use these terms a good deal, and a misunderstanding at the outset can lead to confusion now and later.

Implementation

For the most part, the term implementation is a noun referring to the details—the actual code—in a program. So when referring to an implementation, the reference is to the actual program as it is coded. Implementation is the internal details of a program, but is often used to refer to a method or class itself.

You may run into some confusion when the keyword implements refers to contracting with an interface structure. For example, the line

class MyClass implements IMyInterface

has the effect of passing the interface of IMyInterface to MyClass. To say that MyClass implements IMyInterface really means that MyClass promises to use all the signatures of IMyInterface and add the coding details to them. So the phrase MyClass implemented IMyInterface means that MyClass took all of the methods in IMyInterface and added the code necessary to make them do something while preserving their signatures.

One of the two main principles of design pattern programming is program to an interface, not an implementation. However, you must remember that implementation is employed in different contexts, and its specific meaning depends on these contexts. Throughout this book, you will see references to implementation used again and again with different design patterns, and you need to consider the other elements being discussed where the term is used.

State

The term state is not part of the ActionScript 3.0 lexicon (like implements is), but the term is used in discussing design patterns. Essentially, state is used to refer to an object's current condition. For the most part you will see state used to convey the value of a key variable. Imagine a class with a single method with a Boolean value set in the method. The Boolean can either be true or false. So its state is either true or false—which could represent on/off, allow/disallow, or any number of binary conditions. The exact meaning of true or false depends on the position of the class in the rest of the design pattern. A more specific example would be an object that plays and stops an MP3 file. If the MP3 is playing, it's in a play state, while if it's not playing, it's in a stop state.

One use of state is in the context of a state machine and the State design pattern you will be seeing in Chapter 10. A state engine is a data structure made up of a state network where changes in state affect the entire application. The State design pattern is centered on an object's behavior changing when its internal state changes. The term state here is a bit more contextually rich because it is the key concept around which the design pattern has been built.

In general, though, when you see state used, it's just referring to the current value of an object's variables.

Client and Request

In this age of the Internet and Web, the term client is used to differentiate the requesting source from the server from which a request is being made. We think of client/server pairs. Moreover, the term request is used to indicate that a Web page has been called from the server.

In the context of design patterns, instead of a client/server pair, think of a client/object pair. A request is what the client sends to the object to get it to perform an operation—launch a method. In this context, a request is the only way to get an object to execute a method's operations. The request is the only way to launch an operation since the object's internal state cannot be accessed directly because of encapsulation.

Note

Don't forget Flash Media Server clients and servers! If you work with Flash Media Server 2, you're aware of client-side and server-side programs. You can launch a server-side method with a request from a client-side object. However, you can launch a client-side operation with a client-side request as well and a server-side method with a server-side request. So if you're using Flash Media Server 2, you're just going to have to keep the concepts separate.

In a nutshell, the client is the source of a request to an object's method. A quick example shows exactly what this looks like. Example 1.37, “MyObject.as” and Example 1.38, “MyClient.as” make up an application that does nothing except show a client making a request to an object.

Example 1.37. MyObject.as

package
{
    public class MyObject
    {
        private var fire:String;

        public function MyObject():void {}
        public function worksForRequest():void
        {
            fire="This was requested by a client";
            trace(fire);
        }
    }
}

The MyObject class' sole method is worksForRequest(). The method is encapsulated, so it should have only a single way to launch its operations. Fortunately, all we need to do is create an instance of the class, and add the method to the instance to make it work, as Example 1.38, “MyClient.as” shows.

Example 1.38. MyClient.as

package
{
    import flash.display.Sprite;

    public class MyClient extends Sprite
    {
        var myClient:MyObject;
        public function MyClient():void
        {
            myClient=new MyObject();
            myClient.worksForRequest();
        }
    }
}

The output tells the whole story:

This was requested by a client

The client in this case is simply an instance of the object whose method it requests. The client/object relationship is through the request. Often the client adds specific details through a parameter, but the concept of a request is usually nothing more than invoking the method with an instance of the object that owns the method.

Program to Interfaces over Implementations

To understand why the authors of design patterns encourage programming to interfaces over implementations, you need to first understand the general goal of flexibility and reusability. Second, you need to appreciate the problem of managing dependency in large programs.

As we have noted in this chapter, the overall goal of design patterns is to create reusable code. In order to meet this overall goal, the code must be flexible. This does not mean that your application runs better or compiles faster. All it does is help you create code that you can reuse in other projects. The more time and effort you spend, the larger the team engaged in working with the code, the more important this overall goal,

Managing Dependency

The need for software flexibility leads to the need to manage dependency. When your code depends on a specific implementation, and the implementation changes, your client dependency leads to unexpected results or fails to run altogether. By depending on interfaces, your code is decoupled from the implementation, allowing variation in the implementation. This does not mean you get rid of dependency, but instead you just manage it more flexibly. A key element of this approach is to separate the design from the implementation. By doing so, you separate the client from the implementation as well.

As we have seen, you can use the ActionScript 3.0 interface structure or an abstract class to set up this kind of flexible dependence. However, when you use either, you must be aware of the way in which to manage the dependency. If you use an interface structure, any change in the interface will cause failure in all of the clients that use the interface. For example, suppose you have five methods in an interface. You decide that you need two more. As soon as you add the two new methods to your interface, your client is broken. So, if you use the interface structure, you must treat the interface as set in stone. If you want to add new functionality, simply create a new interface.

The alternative to using the interface structure is to use abstract classes. While the abstract class structure is not supported in ActionScript 3.0 as interfaces are, you can easily create a class to do everything an abstract class does. As we saw in several examples in this chapter beginning with Example 1.3, “PlayVideoAbstract.as”, creating and using an abstract class is simply adhering to the rules that abstract classes follow anyway. For example, abstract classes are never directly implemented.

The advantage of an abstract class over an interface is that you won't destroy a client when you add methods to the base class. All of the abstract function must be overridden to be used in a unique manner, but if your client has no use for a new method, by doing nothing, the method is inherited but not employed or changed. On the other hand, every single method in an interface structure must be implemented.

A further advantage of an abstract class is that you can add default behaviors and even set up concrete methods inherited by all subclasses. Of course the downside of default behaviors and concrete methods is that a subclass may not want or need the default or concrete methods, and a client may end up doing something unwanted and unexpected if any concrete changes are introduced. Whatever the case, though, management of dependency is easier with the flexibility offered by interfaces and abstract classes over concrete classes and methods.

So the decision of whether to use an interface or abstract class depends on what you want your design to do. If the ability to add more behaviors easily is most important, then abstract classes are a better choice. Alternatively, if you want independence from the base class, then choose an interface structure. No matter what you do, though, you need to think ahead beyond the first version of your application. If your application is built with an eye to future versions and possible ways that it can expand or change, you can better judge what design pattern would best achieve your goals.

Using Complex Interfaces

As you saw in Example 1.36, “DoBusiness.as”, the program typed the instances to the implementation and not the interfaces as was done in Example 1.20, “MakeSound.as”. This was caused by the key methods being part of classes that implemented an interface and extended a class. Because of the way in which the different display objects need to be employed, this dilemma will be a common one in using Flash and ActionScript 3.0. Fortunately, a solution is at hand. (The solution may be considered a workaround instead of the correct usage of the different structures in ActionScript 3.0. However, with it, you can create methods that require some DisplayObject structure and program to the interface instead of the implementation.)

If the interface includes a method to include a DisplayObject type, it can be an integral part of the interface. Because Sprite is a subclass of the DisplayObject, its inclusion in the interface lets you type to the interface when the class you instantiate is a subclass of Sprite (or some other subclass of the DisplayObject, such as MovieClip.)

To see how this works, the application made up of Example 1.39, “IVid.as” and Example 1.40, “VidPlayer.as” creates a simple video player. The VidPlayer class builds the structural details of the video playing operations. To do so requires that it subclass the Sprite class. By placing a getter method with a DisplayObject type in the IVid interface, the application sets up a way that the client can program to the interface.

Example 1.39. IVid.as

package
{
    import flash.display.DisplayObject;

    public interface IVid
    {
        function playVid(flv:String):void;
        function get displayObject():DisplayObject;
    }
}

The implementation of the IVid interface includes a key element. The displayObject() function is implemented to the DisplayObject class in building the VidPlayer class.

Example 1.40. VidPlayer.as

package
{
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import flash.media.Video;
    import flash.display.Sprite;
    import flash.display.DisplayObject;

    public class VidPlayer extends Sprite implements IVid
    {
        private var ns:NetStream;
        private var vid:Video;
        private var nc:NetConnection;

        public function get displayObject():DisplayObject
        {
            return this;
        }

        public function playVid(flv:String):void
        {
            nc=new NetConnection();
            nc.connect(null);
            ns=new NetStream(nc);
            ns.play(flv);
            vid=new Video();
            vid.attachNetStream(ns);
            addChild(vid);
        }
    }
}

Keep in mind that a getter method, using the get keyword, looks like a property, and is treated like one. Any reference to displayObject is actually a method request. The trick is to add the instance to the display list, and at the same time call the method that establishes the instance as a DisplayObject. Example 1.41, “DoVid.as” does just that.

Example 1.41. DoVid.as

package
{
    import flash.display.Sprite;

    public class DoVid extends Sprite
    {
        //Type as Interface
        private var showTime:IVid;

        public function DoVid()
        {
            //Play the video
            showTime=new VidPlayer();
            showTime.playVid("iVid.flv");
            //Include DisplayObject instance
            addChild(showTime.displayObject);
            showTime.displayObject.x=100;
            showTime.displayObject.y=50;
        }
    }
}

In reviewing how the process works, first, the showTime instance is typed to the interface, IVid. Next, showTime instantiates the VidPlayer class that has all the details for playing the video. By doing so, it inherits the Sprite class as well as the IVid interface. Then the showTime client plays the video using the playVid() method. Finally, when the showTime instance is added to the display list with the addChild statement, it is added as both the child of the VidPlayer class and the DisplayObject class by using the displayObject getter. Because the getter, displayObject, is included in the display list, you will not get the following error:

1067: Implicit coercion of a value of type IVid to an unrelated type flash.
display:DisplayObject.

IVid appears in the error because the instance was typed to the interface instead of the implementation. By slipping in the DisplayObject typed getter method, we avoid the error.

Favor Composition

Throughout this chapter, we have discussed the principle of programming to the interface instead of the implementation. The second principle of object-oriented design posited by Gamma, Helm, Johnson and Vlissdes (GoF) is: Favor object composition over class inheritance.

To understand this second key principle, we need to understand exactly what composition means, and its advantages over inheritance. After all, the principle is essentially stating that your programs will be better using composition than inheritance.

Does this mean to abandon inheritance? After all, inheritance is one key concept in object-oriented programming. Actually, what GoF discuss is that most programmers rely too heavily on inheritance, and need to use it more in conjunction with composition. Most programmers create new objects from old objects using inheritance, and, through composition, the old and new objects can be used together.

The best way to understand composition is to see it in the context of its use. In this book, both the State and Strategy patterns are built using composition, and they depend on delegation. In the State design pattern, an object delegates requests to an object representing the current state in an application. In the Strategy design pattern, specific requests are delegated to objects representing strategies (algorithms) for solving problems. The great advantage of both these design patterns is that they are flexible and not bogged down in inflexible dependencies.

Doing Composition

To understand composition, we will start with a simple example. In the next sample application you'll see that both inheritance and composition are used together. Each example will show one of the following relationships:

  • 'Is a' relationship: object inherited

  • 'Has a' relationship: object composition

  • 'Uses a' relationship: one object used by another object (instantiated without inheritance or composition.)

In the next section on delegation, we'll look at these relationships. For now, though, each of the classes in the application made up of Example 1.42, “BaseClass.as” through Example 1.44, “DoHasBase.as” show each of these relationships. The comments in the examples identify the type of relationship. First, we establish a base class to be the delegate.

Example 1.42. BaseClass.as

package
{
    public class BaseClass
    {
        public function homeBase()
        {
            trace("This is from the Base Class");
        }
    }
}

Composition includes a reference to another class in a class definition. Example 1.45, “Media.as” shows how a class is set up to use composition. The line

private var baseClass:BaseClass;

keeps the reference to BaseClass in its class definition. That line is the basis of composition. The HasBase class now Has a BaseClass. In this particular implementation, the HasBase class creates an instance of BaseClass in its constructor. Finally, a public function, doBase(), delegates the work back to BaseClass.

Example 1.43. HasBase .as

package
{
    //Composition
    public class HasBase
    {
        private var baseClass:BaseClass;

        public function HasBase()
        {
            baseClass=new BaseClass();
        }
        public function doBase()
        {
            baseClass.homeBase();
        }
    }
}

Now, in Example 1.44, “DoHasBase.as”, the HasBase class is used to delegate an operation back to BaseClass. All this is done without having to inherit any of BaseClass' properties, but HasBase does have a BaseClass. However, HasBase is not a BaseClass.

Example 1.44. DoHasBase.as

package
{
    //Executes the HasBase Composition class
    import flash.display.Sprite

    public class DoHasBase extends Sprite
    {
        private var hasBase:HasBase;
        public function DoHasBase()
        {
            hasBase=new HasBase();
            hasBase.doBase();
        }
    }
}

The advantages of using composition over inheritance are difficult to see in such a small example. Later in the book, when examining the different design patterns, the advantages will become clearer. However, when composition is used with a large project, especially with a design pattern, its advantages begin to make even more sense. You'll really see why composition is preferred over inheritance when you have to make changes to a large, complex program working with several other co-developers.

Using Delegation

Delegation is one of the most important concepts in working with design patterns because by using it, composition can have the same power of reusability as inheritance, with far greater flexibility. Because delegation typically works with inheritance, any examination should not be one where inheritance and delegation are treated as mutually exclusive. Rather, we need to see how they differ and how they are used in conjunction with one another—because that's how they're typically cast in a design pattern.

The clearest way we've seen composition distinguished from inheritance is through describing the relationship between components in an application. If ClassB is subclassed from ClassA, ClassB is described as being a ClassA. However, if ClassB delegates to ClassA through composition, then ClassB can be said to have a ClassA.

You may have a class set up for loading SWF files in a desired configuration. You want the functionality of loading the SWF files in that configuration, but you also want to play audio using MP3 files. Using composition, you could simply create a class that delegates each function to the appropriate classes. Figure 1.7, “Delegating to different classes” shows this relationship:

Figure 1.7. Delegating to different classes

Delegating to different classes


In this situation, inheritance would not be too helpful. You could subclass one class but not both. Of course you could instantiate an instance of each class in the third class, or simply subclass one class and then create an instance of whichever class you didn't subclass. That would be a new class with an "is-a" and a "uses-a" different class for the other functionality. A class is considered a "uses-a" when it creates an instance of another class but does not hold a reference to it.

To understand and best use composition, you need to understand how delegation really works, and to explain, you need to see it in a slightly more realistic example of its use. The application will simulate a media application for playing and recording media files using Flash and Flash Media Server 2 (FMS2). You can play either FLV or MP3 files using FMS2. While you can record FLV files using FMS2, you can't publish MP3 files by themselves. This application is designed for adding future features and reducing dependencies at the same time.

If you are pretty sure that both the media server and your application will change, then you'll want to minimize dependencies by using composition and delegation. For example, suppose that a future version of FMS2 changes, and you can record MP3 files directly. You'd only have to change the RecordAudio class so that it would record audio. By making that change, nothing else in the application would be affected. Alternatively, suppose you have a holographic player that you want to add to the mix. You can easily add another class that will play and/or record holographic images without disturbing the rest of the application.

Example 1.45, “Media.as” though Example 1.54, “TestMedia.as” make up the application. Save all of the .as files in the same folder. It represents a typical use of composition.

Example 1.45. Media.as

package
{
    //Abstract class
    class Media
    {
        //Composition: Reference to two interfaces
        var playMedia:PlayMedia;
        var recordMedia:RecordMedia;

        public function Media() {}

        public function doPlayMedia():void
        {
            //Delegates to PlayMedia
            playMedia.playNow();
        }
        public function doRecordMedia():void
        {
            //Delegates to RecordMedia
            recordMedia.recordNow();
        }
    }
}

Example 1.46. VideoFlash.as

package
{
    //Concrete Media subclass: Video
    class VideoFlash extends Media
    {
        public function VideoFlash()
        {
            //Inherits composition references from superclass
            playMedia = new PlayVideo();
            recordMedia = new RecordVideo();
        }
    }
}

Example 1.47. Mp3.as

package
{
    //Concrete Media subclass: Audio
    public class Mp3 extends Media
    {
        public function Mp3()
        {
            //Inherits composition references from superclass
            playMedia = new PlayAudio();
            recordMedia = new RecordAudio();
        }
    }
}

Example 1.48. PlayMedia.as

package
{
    //Interface for playing media
    interface PlayMedia
    {
        function playNow():void;
    }
}

Example 1.49. PlayVideo.as

package
{
    //Concrete PlayMedia: Video
    class PlayVideo implements PlayMedia
    {
        public function playNow():void
        {
            trace("Playing my video. Look at that!");
        }
    }
}

Example 1.50. PlayAudio.as

package
{
    //Concrete PlayMedia: Audio
    class PlayAudio implements PlayMedia
    {
        public function playNow():void
        {
            trace("My MP3 is cranking out great music!");
        }
    }
}

Example 1.51. RecordMedia.as

package
{
    //Interface for recording media
    interface RecordMedia
    {
        function recordNow():void;
    }
}

Example 1.52. RecordVideo.as

package
{
    //Concrete RecordMedia: Video
    class RecordVideo implements RecordMedia
    {
        public function recordNow():void
        {
            trace("I'm recording this tornado live! Holy....crackle, crackle\n");
        }
    }
}

Example 1.53. RecordAudio.as

package
{
    //Concrete RecordMedia: Audio
    class RecordAudio implements RecordMedia
    {
        public function recordNow():void
        {
            trace("Rats! I can't record MP3 by itself.\n");
        }
    }
}

Example 1.54. TestMedia.as

package
{
    import flash.display.Sprite;

    public class TestMedia extends Sprite
    {
        public function TestMedia()
        {
            var delVideo:Media=new VideoFlash();
            delVideo.doPlayMedia();
            delVideo.doRecordMedia();

            var delAudio:Media = new Mp3();
            delAudio.doPlayMedia();
            delAudio.doRecordMedia();
        }
    }
}

Create a Flash document, save it in the same folder with the class files, and, in the Document class window, type in TestMedia. When you test it, the Output window simulates the behaviors.

Playing my video. Look at that!
I'm recording this tornado live! Holy....crackle, crackle

My MP3 is cranking out great music!
Rats! I can't record MP3 by itself.

The algorithms you'd have in an actual application would be more complex than the simple trace statements. However, no matter how complex they got, the dependencies are set up so that a change in one would only affect those elements in the application that you want to change, and not those you don't want to change.

Making Composition, Inheritance, and Instantiation Work Together

We haven't compared the relative advantages and disadvantages between composition and inheritance because with composition, both composition and inheritance operate in the same environment. For that matter, so too does instantiation where one class simply instantiates an instance of another class. The idea that one would work with one and exclude the other was never the point made by GoF in their principle to favor composition over inheritance. Yes, stated that way, that's what the principle sounds like. However, in explaining the principle, the founders of design patterns not only explicitly point out that composition and inheritance work together, their design patterns show it.

The application in Example 1.45, “Media.as” through Example 1.54, “TestMedia.as” was used to illustrate composition. It also shows inheritance and instantiation at work. To see this relationship better, consider Figure 1.8, “Relationships of composition, inheritance, and instantiation”.

Figure 1.8. Relationships of composition, inheritance, and instantiation

Relationships of composition, inheritance, and instantiation

In Figure 1.8, “Relationships of composition, inheritance, and instantiation”, you can see that the Media class delegates to the RecordMedia class. It does this by holding a reference to that class in its definition. (See Example 1.45, “Media.as”.)

var recordMedia:RecordMedia;

In the VideoFlash class, you can see that it inherits from the Media class. At the same time, though, VideoFlash instantiates RecordVideo.

recordMedia = new RecordVideo();

In turn, RecordVideo inherits from RecordMedia. At this point, we're right back to the class to which the Media class first delegated, RecordMedia.

Using composition without inheritance is difficult to imagine in most practical applications. Thus, instead of focusing on the relative advantages of each, for now consider composition and inheritance a team. In Chapter 11, we again consider this issue of favoring composition over inheritance in the context of using composition with design patterns.

Maintenance and Extensibility Planning

A number of years ago, we built a web site designed for change. At the time, no thought was given to design patterns, but instead we knew that the site would change based on experience, and were acutely aware of making sure that the site was set up for accepting change without having to redo it.

This was a case where the plan worked too well. The site is easy to update and as a result, we really haven't bothered to take the time to rework the site to incorporate new concepts. It's starting to look a little old-fashioned, and we'd like to upgrade the version of Flash so that we can optimize video and all the new features in Flash CS3 and ActionScript 3.0. However, the plan illustrates a basic truth about software in general and web development in particular. You're going to spend more time on maintenance and changing a site than you are building it in the first place. As a result, you need to plan for maintenance and extensibility, and not just to get things working right in the first place.

Had the web site that had been planned for change been done with design patterns, not only would it be able to adapt to change, we wouldn't have to scrap the current design and start all over from scratch to extend the site. That is, only part of the planning process should address change of a static category. You also need to plan for extending the site to incorporate more than what you originally planned to change. That is, you may need to add new materials to change.

Planning Only for Maintenance

While the web site described as built for change has persisted, it has not evolved. The site has been easy to maintain because its main function loads an image, a text file, and menu items for a given product. By either changing the label on an existing button or adding a button, changing and adding products is pretty simple as well. So it has a little extensibility in the sense that its not fixed to a single product.

However, the site really isn't set up for robust extensibility. For instance, adding a blog to the site or changing the way that the menus work would take the same amount of re-structuring as it would to start from scratch. So, while changing products in the site is simple, changing the site is not. Structurally, the site looks something like Figure 1.9, “The Big Function”—The Big Function:

Figure 1.9. The Big Function

The Big Function


It doesn't matter whether the big function is directly instantiated, gained through inheritance, or a delegate of composition. It has no flexibility other than the parameter to tell it what to place on the stage. If its algorithm is changed, it could wreck havoc on its use. You can view it as tough and inflexible because it gets one job done in one way. Alternatively, the big function can be seen as dainty and fragile because of its dependency on a single routine, and because it is subject to freeze up when interacting with new elements in the application. In any case, it doesn't lend itself to a flexible site, and we should rethink it.

Adding Extensibility to a Plan with Granularity

The plan using The Big Function, even though it has limited flexibility, is bound to break down and fail in the long run. To avoid getting stuck with an inalterable application, you need to consider some granularity in your design. In this context granularity refers to the amount of functionality each of your classes has. The trade-off between full functionality and granularity is that the more functionality a class has, the more it will do all by itself. After all, most classes we create are developed to add the functionality of several built-in classes. However, sometimes less is better. The less functionality a class has, the more components in your application its functionality can employ. Figure 1.10, “Granular functions” shows how this granularity might work.

Figure 1.10. Granular functions

Granular functions

The Big Function from the last section has been broken down into three smaller (more granular) functions. Using composition, the functionality of the Big Function is duplicated. However, the granularity gives the developer far more options in the future. In the context of developing a real-world application, your design must look over the horizon. That is, you need to plan for both possible and unknown changes. Figure 1.10, “Granular functions” shows some possible future extensions to the application (those with dashed lines). The value of granularity is that the new classes can use some or all of the more granular functions. That would be impossible with the single Big Function from the previous section. Likewise, new functions can be added and used with the old ones.

Your Application Plan: It Ain't You Babe

Jennifer Tidwell, in talking about interface design, reminds the designer that whatever else is true, the designer/developer is not the audience (or the client for that manner). Before pulling out your program development tools, you really need to get together with the client and find out what the client wants. The image of the programmer as the guy in the basement with a ponytail (whom we don't let talk to our clients) simply isn't a workable model, especially when you're planning for the Web.

Because the Web and Web-related technology are always changing, as clearly evidenced by the changes in Flash and ActionScript over recent years, what can and cannot be accomplished by any software tool is always changing. Because the developer is responsible for knowing the limits and possibilities of software better than anyone, she needs to be part of the process. In larger firms, this role is part of a graphic design, interface design, human computer interaction (HCI) designers, and information design team. In smaller firms, the developer may have to fill several roles, but whatever the arrangement, developers need to be part of the process interacting with the client whose business or organization depends on accomplishing a goal. The better the developer understands what the client wants and the better he can communicate the opportunities and limitations of the software to accomplish the goals, the more likely the software produced will accomplish what the client needs for success.

Using OOP and Design Patterns to Meet Client Goals

The role of object-oriented programming and design patterns is to help the software developer plan for creating a site that keeps the client's site healthy. Keeping a site in good shape depends on the capacity to regularly update it, and to expand it when needed. Too often developers think of a web site as static, but sites are dynamic, living entities—or at least need to be conceived that way. Design patterns constitute a set of plans—architectural designs if you will—that provide the tools to keep web sites alive.

Flexibility is inherent in software reusability. Both OOP and design patterns were developed with the goal of both reusability and flexibility, and if an application is approached in the most practical manner imaginable, then design patterns make a great deal of sense. So rather than being a set of strict rules for creating great software, design patterns represent flexible tools for creating exactly the kind of site your client needs.

Choosing the Right Design Pattern

Choosing the best design pattern for a particular situation is as much an art as it is a formula. Throughout the book, you'll see that we've included a wide variety of examples, and you may even see a few similar examples with different design patterns. The reason is that most development challenges can be approached from more than a single angle. From one angle, a solution seems good and natural, but from a different angle, another solution seems better. For example, a major project employing a design pattern involved a video player that would be able to play, record, stop, and pause a video using Flash Media Server 2. The solution originally seemed to lie in state machine because of a related project. The "fit" between what needed to be done and the concepts in a state machine seemed to be perfect. From there it was a simple step to the State design pattern. It was tested as a solution, and it worked so well, and had the required flexibility, that it was adopted as the right solution.

As you go through the examples in the book, you'll see that the patterns have been organized into three parts: creational, structural, and behavioral. The parts in the book describe the general categories for the design patterns. The chapters within the parts explain how to create the designs in ActionScript 3.0, and give examples and explanations of their actual use.

In addition to organizing the design patterns into the purposes for which the patterns are designed, the Gang of Four also classified the patterns by scope. Scope refers to whether the pattern applies primarily to object or class. In selecting the design patterns for this book, we selected representative patterns from each of the matrices that these class and object classifications represent. Table 1.1, “Design pattern classifications”shows the design patterns chosen for this book organized by purpose and scope.

Table 1.1. Design pattern classifications

    

Purpose

 
   

Creational

Structural

Behavioral

 

Class

 

Factory Method

Adapter (class)

Template Method

Scope

     
 

Object

 

Singleton

Adapter (object)

Command

    

Composite

Observer

    

Decorator

State

     

Strategy


Achieving Better OOP Through Design Patterns

While this chapter has provided an introduction to key OOP concepts for those who are relatively new to OOP, learning the design patterns should prove useful in learning OOP as well. We might even venture to add that if this is your initial introduction to OOP, you will find what some consider the correct way to understand OOP. (Others might even contend that it is the only way to understand OOP.) We spent time on OOP because a sizable portion of ActionScript programmers may not have gotten around to its use yet. After all, ActionScript itself is relatively new to OOP structures.

Alternatively, if you're an old trooper with OOP, either from ActionScript or another language such as Java or C++, we hope that our discussion of design patterns will help you better apply OOP concepts in a design pattern.

Whatever your background in OOP, the Gang of Four recommend the following design patterns for those of you who are relatively inexperienced in object-oriented programming:

However, if these patterns seem in any way daunting, do not worry. We don't know of anyone who fully grasped design patterns on the first go-around, including the patterns suggested by GoF. We certainly didn't. However, by using, changing and experimenting with the examples we have provided, along with going over the explanation of OOP and design pattern concepts in the chapters, we believe that you'll come to see them in the same light as we do—the best friend a programmer could have.

If you enjoyed this excerpt, buy a copy of ActionScript 3.0 Design Patterns.