This is part 1 of a two part composite pattern example implemented in ActionScript 3. The composite pattern comes in handy to develop complex structures consisting of simpler parts or components. The components can be primitive objects or they can be containers that hold other components. An example will help explain this better. Think of a house as a composite object. A house consists of a roof and several rooms (living spaces). Each room can contain windows, chairs etc. The house and rooms are composite objects as they contain other components. However, we can consider the roof, windows, beds and chairs as primitive objects. The composite pattern makes building, accessing, and manipulating composite structures simple by enabling the client to treat both composite objects (that contain other objects) and components (primitive objects) the same way through a common interface.
The following example develops a book in Flash. A book is a composite object as it contains several pages. This is by no means a functional book, but a conceptual example to illustrate the utility of the composite pattern. One of the big advantages of design patterns is extensibility: allowing the application to expand and add new features without breaking existing functionality. We will extend this example in part 2 by adding composite pages that can contain text and images.

The class diagram of the Book example shows the BookComponent class that should behave as an abstract class. ActionScript 3 does not have abstract classes. See a previous post on Runtime Checks for Abstract Classes and Methods in ActionScript 3. I’m not implementing the runtime checks in this example, but noting that some concrete classes need to be treated as abstract. BookComponent is the abstract interface that defines the default behavior for both primitive and composite objects. It defines the default implementations for the add, getChild, and remove methods used to build and manipulate composite structures. The print method is an operation that is unique to the book.
A book is a good example of a composite pattern. It contains chapters that can be considered composite objects that hold collections of pages. Pages can be considered indivisible primitives. We will first implement a very basic book as a composite tree and then extend it to demonstrate the power of the composite pattern.
The BookComponent.as file contains the BookComponent abstract class that defines the interface for both primitive objects such as pages and composite objects such as chapters. The Page.as file contains the Page class, and the BookComposite.as file contains the BookComposite class. Both Page and BookComposite classes extend the BookComponent class and provide necessary implementations. The Main.as file is contains the client class Main (also the document class for the Flash document).
BookComponent.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package { import flash.errors.IllegalOperationError; // ABSTRACT Class (should be subclassed and not instantiated) public class BookComponent { public function add(c:BookComponent):void { throw new IllegalOperationError("add operation not supported"); } public function remove(c:BookComponent):void { throw new IllegalOperationError("remove operation not supported"); } public function getChild(n:int):BookComponent { throw new IllegalOperationError("getChild operation not supported"); return null; } // ABSTRACT Method (must be overridden in a subclass) public function print():void { } } } |
In addition to defining the default implementations for the add, remove, and getChild methods, the BookComponent class defines the abstract method print that can print the book component.
BookComposite.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package { public class BookComposite extends BookComponent { internal var sName:String; private var aChildren:Array; public function BookComposite(sName:String) { this.sName = sName; this.aChildren = new Array(); } override public function add(c:BookComponent):void { aChildren.push(c); } override public function getChild(n:int):BookComponent { if ((n > 0) && (n <= aChildren.length)) { return aChildren[n-1]; } else { return null; } } override public function print():void { for each (var c:BookComponent in aChildren) { c.print(); } } } } |
The BookComposite class extends the BookComponent class. Whenever a BookComponent is added to a composite node via the add method it is pushed into the aChildren array. The BookComposite class implements the abstract method print that prints all child nodes by recursively traversing the whole composite tree represented by the book. The remove method has not been implemented, in order to keep things simple.
Page.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package { public class Page extends BookComponent { private var sName:String; public function Page(sName:String) { this.sName = sName; } override public function print():void { trace(this.sName); } } } |
The Page class represents a page of the book and implements the print method by simply tracing the name of the page passed to it in the constructor.
Main.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | package { import flash.display.Sprite; /** * Main Class * @ purpose: Document class for movie */ public class Main extends Sprite { public function Main() { // create book var book:BookComposite = new BookComposite("book"); // add the table of contents book.add(new Page("table of contents")); // create a chapter book.add(new BookComposite("chapter 1")); // add chapter to book book.getChild(2).add(new Page("Chapter 1 title page")); // add a Page book.getChild(2).add(new Page("page 2")); // add a Page book.getChild(2).add(new Page("page 3")); // add a Page // create another chapter book.add(new BookComposite("chapter 2")); // add chapter to book book.getChild(3).add(new Page("Chapter 2 title page")); // add a Page book.getChild(3).add(new Page("page 5")); // add a Page book.getChild(3).add(new Page("page 6")); // add a Page book.getChild(3).add(new Page("page 7")); // add a Page // add an appendix book.add(new Page("appendix")); // add an index book.add(new Page("index")); trace("print book"); trace("=========="); book.print(); } } } |
The client class Main builds the book by first creating a root composite node that will contain the entire book. It then adds a table of contents page, two chapters that contain several pages, an appendix and index page. Finally it prints the whole book by calling the print operation on the root node.
Note how component and composite nodes are added to the book through a single interface using the add method. The client does not need to worry about the type of node as the composite pattern treats both composite and component nodes the same way. The following output is generated by the print operation.
1 2 3 4 5 6 7 8 9 10 11 12 | print book ========== table of contents Chapter 1 title page page 2 page 3 Chapter 2 title page page 5 page 6 page 7 appendix index |
Download the source: book_p1.zip. Watch for part 2 of this article, where the book will be extended to incorporate composite pages that can contain both text and images.

The Composite Pattern: Book (Part 1) by Chandima Cumaranatunge, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.


Bill Sanders
Hello! Good Site! Thanks you! sldbroxxud
book_p1.zip doesn’t work. I get the following error: 5006: An ActionScript file can not have more than one externally visible definition: Main, Test
Sorry about that Bora - bug in the class definition. Fixed!
how about showing how to remove a page?… its nice that you can add etc.. what about removing and updating the book? how would this work?
Jon
Hi Jon, Take a look at the second installment of the composite. The comments section has a discussion on how to remove nodes.
http://www.as3dp.com/2007/08/05/composite-pattern-extending-the-book-to-add-composite-pages-part-ii/