Composite Pattern: Extending the Book to include Composite Pages (Part II)

This is part two of a two part series on developing the structure of a book using the composite pattern. In part 1, we treated pages as primitive component objects. However, pages can be further decomposed to contain text blocks and images. This example illustrates the utility of the composite pattern by demonstrating how easy it is to extend an application that implements the composite pattern by adding new composite and component classes that extend existing ones.

I must reiterate that this is a conceptual example. To keep things simple, the images in our composite page will be drawn as ASCII art. We will extend the previous book application (from part 1) by adding three new classes called: CompositePage, TextBlock and Figure.
The recommended way to add multiple composite classes is to extend a generic composite class such as BookComposite. Composite classes implement the housekeeping methods required of composite nodes such as add and getChild. Therefore, it makes sense to inherit the implementations for these methods and override the operations, such as print, that require custom functionality. The CompositePage.as file contains the CompositePage class that extends the BookComposite class defined previously.
CompositePage.as

package {
 
	public class CompositePage extends BookComposite {
 
		public function CompositePage(sName:String) {
			super(sName);
		}
 
		override public function print():void {
			trace(this.sName);
			super.print();
			trace("--------------------");
		}
	}
}

Note that the CompositePage class overrides the print() method and prints a dividing line to separate one page from the next. This is the only unique feature in this CompositePage composite class when compared to the BookComposite class. All the functionality required by a composite object such as adding, removing and accessing children are inherited.
Both the TextBlock and Figure classes are primitive component objects that extend the BookComponent abstract class.

TextBlock.as

package {
 
	public class TextBlock extends BookComponent {
 
		private var sText:String;
 
		public function TextBlock(sText:String) {
			this.sText = sText;
		}
 
		override public function print():void {
			trace(this.sText);
		}
	}
}

The TextBlock class extends the BookComponent abstract class. Text blocks are primitive objects that can be added to a composite page.

Figure.as

package {
 
	public class Figure extends BookComponent {
 
		private var sName:String;
		private var textFigure:String;
 
		public function Figure(sName:String, textFigure:String) {
			this.sName = sName;
			this.textFigure = textFigure;
		}
 
		override public function print():void {
			trace(this.textFigure);
			trace(this.sName);
		}
	}
}

Similarly, the Figure class extends the BookComponent abstract class. It declares a property called sName that holds the name of the figure. Another property called textFigure holds the figure. For simplicity, the figures in the book will be drawn with text as ASCII art. As before, figures are primitive objects that can be added to a composite page.

Creating a book with composite pages
Lets create a new book using the new composite page class. The following statements should be run from the client. The client can be the document class in a Flash document (.fla) that builds a composite structure. Note that, in addition to the three new classes defined in this article, the following statements also use classes defined in part 1.

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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");  
 
			// create table of contents page
			book.add(new CompositePage("Table of contents:")); // add TOC
			var t:String = "Chapter 1 ... page 1\r";
			t += "Chapter 2 ... page 4";
			book.getChild(1).add(new TextBlock(t));
 
			// create a chapter
			book.add(new Chapter("chapter 1"));
			book.getChild(2).add(new Page("Chapter 1: Farm Animals"));
 
			// create a new composite page
			var p2:CompositePage = new CompositePage("page 2");
			t = "         (__)\r         (oo)\r  /-------\\/\r";
			t += " / |     ||\r*  ||----||\r   ~~    ~~";
			p2.add(new Figure("fig 1: cow", t));
			t = "Cows have one\r";
			t += "stomach, with four\r";
			t += "compartments.";
			p2.add(new TextBlock(t));
			book.getChild(2).add(p2); // add to chapter 1
 
			// create another composite page
			var p3:CompositePage = new CompositePage("page 3");
			p3.add(new Figure("fig 2: duck", ">(o)__\r (_~_/\r ~~~~~~~"));
			t = "Ducks are aquatic\r";
			t += "birds, smaller than\r";
			t += "their relatives the\r";
			t += "swans.";
			p3.add(new TextBlock(t));
			book.getChild(2).add(p3); // add to chapter 1
 
			// create another chapter
			book.add(new Chapter("chapter 2"));
			book.getChild(3).add(new Page("Chapter 2: Work in Progress")); // add a child Page
			book.getChild(3).add(new Page("page 5")); // add a child Page
			book.getChild(3).add(new Page("page 6")); // add a child Page
			book.getChild(3).add(new Page("page 7")); // add a child 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 first adds a new composite page for the table of contents (line 17). It then adds a new text block to the table of contents page (line 20). A new chapter is created next (line 23) with a chapter title page added to it (line 24). Two composite pages are created (lines 27 and 38) with both text blocks and figure are added to them. Both composite pages are then added to the chapter (lines 35 and 45). Calling the print() operation from the root node results in the following output.

print book
==========
Table of contents:
Chapter 1 ... page 1
Chapter 2 ... page 4
--------------------
Chapter 1: Farm Animals
--------------------
page 2
         (__)
         (oo)
  /-------\/
 / |     ||
*  ||----||
   ~~    ~~
fig 1: cow
Cows have one
stomach, with four
compartments.
--------------------
page 3
>(o)__
 (_~_/
 ~~~~~~~
fig 2: duck
Ducks are aquatic
birds, smaller than
their relatives the
swans.
--------------------
Chapter 2: Work in Progress
--------------------
page 5
--------------------
page 6
--------------------
page 7
--------------------
appendix
--------------------
index
--------------------

Our simple book contains ASCII art and prints using a monospaced front to the Output Panel (like the old dot-matrix printers that take roll paper). However, we can modify the Figure class to display actual images, and create new text block components with different formatting for headers and lists etc. We can also initiate a real print job using the PrintJob class to make this a more functional book.

Changing requirements in software design is a certainty (coupled with death and taxes). OOP and design patterns allow anticipated and unanticipated changes to be managed effectively. As we have seen in this example, extending complex hierarchical structures is easy when they implemented using the composite pattern. The ease is due very much to the common interface shared by both primitive and composite nodes. Adding, deleting and otherwise manipulating the structure is simple - no matter whether you are dealing with component or composite objects as both types can be treated the same way. Only, unique functionality in the new objects need to be implemented while the others can be inherited.

Download the source: Book-part_II.zip

6 Responses to “Composite Pattern: Extending the Book to include Composite Pages (Part II)”


  1. 1 Pdazzle

    Great book / site.

    How about a part three dealing with handling events in the composite pattern? Would be really helpful.

  2. 2 chandima

    Thanks for the note. If you are interested in how events can cascade down a complex composite structure; For example, when a composite view is incorporated into a model-view-controller pattern, our MVC chapter 12 has some detail on that. The MVC chapter is available as a free download on Adobe DevNet:

    http://www.adobe.com/devnet/actionscript/articles/ora_as3_design_patterns.html

  3. 3 Ted H.

    Hi! First, TERRIFIC book! I am new to FLEX and Actionscript (come from a .NET background) and this book has really helped.

    I have two questions for you re: the Composite Pattern - one about the pattern itself, one more Actionscript related.

    First, I am creating a data structure that’s essentially like a DOM-tree and I don’t know at node-creation-time if the node “is” a branch or a leaf; so I’m making all my nodes branches (composites). I figured that a leaf is a leaf by simple fact that it has no children, but then I was afraid I’d need to “convert” it to composite if the user decided later they wanted to add child nodes to it.

    For example, take a bulleted list (…). Most of the time the list item will just contain text (and be a leaf), but some of the time, the list item will contain lots of child nodes as well - a table, another list inside a table (which is wrapped in and and )…

    Is this a common scenario with the Composite Pattern? I’ve not read anyone else comment on this problem so I assume either I’ve got it wrong, it’s not a common problem, or all my nodes are REALLY Composite nodes (even if they don’t have Children) simply because they CAN have Children.

    Any thoughts?

    My second question regards removing nodes. You recursively loop through the child axis to remove nodes to release memory. I find this confusing. If I simple remove reference to a child node from it’s parent (in the parent’s child nodes array), does this not destroy the children in this axis?

    I mean, do you really need to go through and kill every child node individually to release memory? If so, can you offer any more detail about this? I was surprised to hear I needed to do the recursive loop (although maybe I just don’t understand something there too).

    Many thanks - again, terrific book (and BLOG!).
    -Ted

  4. 4 chandima

    Hi Ted, quite an interesting question. I think that one would implement a true component to intentionally prevent it from having any children. If you are not quite sure, then all nodes should probably be composites.

    A good example is the display list in Flash as it implements a composite pattern. The component nodes in the display list inherit from the DisplayObject class. The composite nodes inherit from the DisplayObjectContainer class. The Sprite, Loader and Stage classes extend the DisplayObjectContainer class as we want them to have child components as in the case of nested sprites. In contrast, the Bitmap, Shape and StaticText classes extend the DisplayObject class and are true leaf nodes as for example, we don’t want to have nested objects inside a StaticText field.

    You are absolutely correct on the second point. I erred on the side of caution as I wasn’t quite sure of how (and when) Flash garbage collection worked. I wish I’d looked at Grant Skinner’s article on mark sweeping which, clearly shows that an isolated collection of objects referencing each other will also get garbage collected.

    http://gskinner.com/blog/archives/2006/06/as3_resource_ma.html

    I was a little paranoid about leaving a node branch out there with nothing to do.

    Glad you like the book.

    -Chandima

  5. 5 CG

    This book is superb!!

    How would I remove a single, specific component from a composite? This is the closest I’ve come:

    override public function remove(cv:CountryComponent):void {
    	for (var i:uint=0; i < _cvChildren.length; i++) {
    		if (_cvChildren[i] == cv) {
    			_cvChildren.splice(i,1);
    			removeChild(cv);
    			trace("removed component: " + cv);
    		}
    	}
    }

    Also - re: your last reply, if we want to implement a “remove all” we can just remove the composite, correct? or do I have to script a removeAll that loops through _cvChildren and runs remove() for each component?

  6. 6 chandima

    Hi CG, try executing the removeChild before splicing the component from the _cvChildren array because there is a chance that ‘cv’ can get garbage collected as soon as it is spliced. The following sequence inside the loop should work:

    removeChild(cv);
    trace("removed component: " + cv);
    _cvChildren.splice(i,1);
    break;

    You are correct. Simply removing the composite should suffice for garbage collection, as long as there are no external references to any of its child components.

Leave a Reply