O'Reilly logo

ActionScript 3.0 Design Patterns by Chandima Cumaranatunge, William Sanders

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

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

Start Free Trial

No credit card required

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.

Example 1-13. QuadPets.as

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

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

Example 1-14. Dog.as

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

Example 1-15. Cat.as

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

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

Example 1-16. TestPets.as

package
{
    import flash.display.Sprite;

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

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

QuadPets is instantiated

That output is caused by the line:,

trace("QuadPets is instantiated");

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

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

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

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

Using Interfaces and Abstract Classes in ActionScript 3.0

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

Interface constructs

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

To begin to see what an interface does, a simple example illustrates the use of one. However, once you start seeing how they're used in design patterns, you will better see their utility. Example 1-17 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.

Example 1-17. BandFace.as

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

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

Example 1-18. Guitar.as

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

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

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

Example 1-19. Bongo.as

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

    public class Bongo implements BandFace
    {

        public function Bongo(){}

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

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

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

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

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

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

Example 1-20. MakeSound.as

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

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

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

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

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

Note

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

Abstract classes and overriding inheritance

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

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

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

Example 1-21 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.

Example 1-21. AbstractClass.as

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

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

Example 1-22. Subclass.as

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

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

Example 1-23. ImplementSub.as

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

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

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

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

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

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

Why use interfaces and abstract classes?

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

To illustrate what this means, consider the application in Example 1-21 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.

Example 1-24. SubclassChange.as

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

Note that instead of having a single trace statement, Example 1-24 uses three. This modification simulates a more complex change. However, Example 1-25, which implements the application, is identical to Example 1-23.

Example 1-25. ImplementSubChange.as

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

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

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

All the changes are revealed in the line,

doDemo.abstractMethod();

Even though the line is the same as Example 1-23, 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.

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

Start Free Trial

No credit card required