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

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.

Example 1-42. BaseClass.as

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

Composition includes a reference to another class in a class definition. Example 1-45 shows how a class is set up to use composition. The line

private var baseClass:BaseClass;

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

Example 1-43. HasBase .as

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

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

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

Example 1-44. DoHasBase.as

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

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

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

Using Delegation

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

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

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

Delegating to different classes

Figure 1-7. Delegating to different classes

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

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

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

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

Example 1-45. Media.as

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

        public function Media() {}

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

Example 1-46. VideoFlash.as

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

Example 1-47. Mp3.as

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

Example 1-48. PlayMedia.as

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

Example 1-49. PlayVideo.as

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

Example 1-50. PlayAudio.as

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

Example 1-51. RecordMedia.as

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

Example 1-52. RecordVideo.as

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

Example 1-53. RecordAudio.as

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

Example 1-54. TestMedia.as

package
{
    import flash.display.Sprite;

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

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

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

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

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

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

Making Composition, Inheritance, and Instantiation Work Together

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

The application in Example 1-45 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.

Relationships of composition, inheritance, and instantiation

Figure 1-8. Relationships of composition, inheritance, and instantiation

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.

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