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.
The life history of the individual is first and foremost an accommodation to the patterns and standards traditionally handed down in his community.
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.
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 shows how to use the Document class window in the Properties panel to enter the name of the class you want to launch:
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:
Open a new Flash document and save it as rocket.fla.
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.
In the Symbol Edit Mode, draw a rocket on the stage with the dimensions W=89, H=14, as shown in Figure 1-2. Once finished, position the drawing at X=0, Y=0. Click the Scene 1 icon to exit the Symbol Edit Mode.
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 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 ModeDrag 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.
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.
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 shows what you should see:
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.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 in the TestRocket.as file, and save the file once again:Example 1-1. TestRocket.aspackage { 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); } } }
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 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.
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 shows essentially the same application
abstracted to a “description” of what it requires to work.
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 shows how concrete instances are implemented from an abstract class:
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 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 is not encapsulated and will potentially embarrass your dog object:
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 breaks into the object and changes it in ways you don’t want:
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 changes the
NoEncap
class by adding the private
statement to the variables:
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 shows the new
test class, TestEncap
, for the Encap
class.
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 graphically shows the makeup of a typical 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, 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, 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.
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, 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 shows how to interface with the encapsulated dog object:
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 and Example 1-12 show
how you can lock up your encapsulated objects using getters and setters with
the get
and set
methods.
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, 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.
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 through
Example 1-10. 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. 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, 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 through Example 1-16 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.
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.
package { public class Dog extends QuadPets { public function Dog():void { } public function bark():void { trace("Dog class: Bow wow"); } } }
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.
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 shows how a typical interface is created. The application is made up of Example 1-17 to Example 1-20.
The first thing we’ll do is to make our interface. As you can see in Example 1-17, 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.
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 is the first
implementation of the of the BandFace
interface.
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 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.
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,
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.
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 through Example 1-23 make up an application that illustrates how an abstract class works. The abstract class has two methods; a concrete one and an abstract one.
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.
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
.
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 to Example 1-23 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 and Example 1-25
show the two new classes created to make the change.
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 uses
three. This modification simulates a more complex change. However, Example 1-25, which implements the application, is
identical to Example 1-23.
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, 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 through Example 1-25, 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 through Example 1-31 provide a simple example of such an application. The
root abstract class is named Polymorphism
in
honor of the concept it illustrates.
package { //Abstract class public class Polymorphism { public function myMusic():void { //Reserve details for subclasses } } }
package { public class Rock extends Polymorphism { override public function myMusic():void { trace("Play Jimmie"); } } }
package { public class Classic extends Polymorphism { override public function myMusic():void { trace("Play Mozart"); } } }
package { public class Country extends Polymorphism { override public function myMusic():void { trace("Play Willie"); } } }
package { public class Jazz extends Polymorphism { override public function myMusic():void { trace("Play Coltrane"); } } }
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 through Example 1-36 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.
package { public interface IBiz { function productDescribe():String; function productPrice(price:Number):String; function productDisplay(product:String):void; } }
Example 1-33 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.
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 with Example 1-34,
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 is set up to play a video and Example 1-34 a sound. However, note that the identical signatures
are maintained in both examples.
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 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.
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, 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 you can see how an interface structure is programmed
to the interface instead of an implementation.)
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:
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 and Example 1-38 make up an application that does nothing except show a client making a request to an object.
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 shows.
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, 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, the program typed the instances
to the implementation and not the interfaces as was done in Example 1-20. 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 and
Example 1-40 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.
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.
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 does just
that.
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 through Example 1-44 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.
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 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
.
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, 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
.
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 shows this relationship:
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 though Example 1-54 make up the
application. Save all of the .as
files in the
same folder. It represents a typical use of composition.
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(); } } }
package { //Concrete Media subclass: Video class VideoFlash extends Media { public function VideoFlash() { //Inherits composition references from superclass playMedia = new PlayVideo(); recordMedia = new RecordVideo(); } } }
package { //Concrete Media subclass: Audio public class Mp3 extends Media { public function Mp3() { //Inherits composition references from superclass playMedia = new PlayAudio(); recordMedia = new RecordAudio(); } } }
package { //Interface for playing media interface PlayMedia { function playNow():void; } }
package { //Concrete PlayMedia: Video class PlayVideo implements PlayMedia { public function playNow():void { trace("Playing my video. Look at that!"); } } }
package { //Concrete PlayMedia: Audio class PlayAudio implements PlayMedia { public function playNow():void { trace("My MP3 is cranking out great music!"); } } }
package { //Interface for recording media interface RecordMedia { function recordNow():void; } }
package { //Concrete RecordMedia: Video class RecordVideo implements RecordMedia { public function recordNow():void { trace("I'm recording this tornado live! Holy....crackle, crackle\n"); } } }
package { //Concrete RecordMedia: Audio class RecordAudio implements RecordMedia { public function recordNow():void { trace("Rats! I can't record MP3 by itself.\n"); } } }
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 through Example 1-54 was used to illustrate composition. It also shows inheritance and instantiation at work. To see this relationship better, consider Figure 1-8.
In Figure 1-8, 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.)
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:
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 shows how this granularity might work.
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 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-1shows the design patterns chosen for this book organized by purpose and scope.
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.
Get ActionScript 3.0 Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.