JavaScript Design Patterns: Composite

My last post was about the Bridge Design Pattern, which continued the JavaScript Design Patterns series that started off with the Singleton. Today we’ve moved onto the Composite Pattern. Composites are quite useful. By definition of the word “composite”, Composites are _composed _of multiple parts to create one whole entity.

These are the two main benefits that the Composite pattern provides:

You can treat the whole collection of objects the same way you would treat any of the individual objects in the collection. Functions executed on the composite are passed down to each of the children to be executed. On large collections, this becomes very beneficial (though also may be misleading, because you may not realize how large the collection is and therefore not understand how much performance may suffer).

It organizes the objects into a tree structure, and since each composite object contains a method to get its children, you can hide the implementation and organize the children in any way you wish.

Structure of the composite pattern

In the Composite patterns hierarchy, there are two types of objects: leaf and composite. The image below shows an example of the Composite’s structure. It is recursive, which is what gives this pattern its power. The difference between the composite objects and the leaves are that leaves do not have any children, while the very essence of a composite is that it has children.

Composite Design Pattern Structure

Examples of the Composite Pattern

There a number of somewhat common examples of the Composite pattern. If you’ve ever used a PC, you’ve more than likely seen a frequently used implementation of this pattern: the file structure. Consider every disk/drive and folder to be a composite object and every file to be a leaf. When you try to delete a folder, it’ll not only delete that folder, but also every other folder and file contained within it. You really don’t get a much better example than that.

Another example that is a special type of composite is the Binary Tree. If you do not know what that is, you may want to see this Wikipedia article on the Binary Tree. It is special because each node can contain at most 2 children. Also the leaf and composite pieces are exactly the same. Composites represent an end value, just like the leaves and the leaves can become composites at any time by giving them children. It is also optimized to be primarily used to search through sortable data.

If you look around, I’m sure you’ll see some more examples. You might even see some implemented using JavaScript.

Our JavaScript Example of the Composite Pattern

To demonstrate the Composite pattern to you using JavaScript, I will use neither of the above examples. Instead I will make an image gallery. It is actually quite similar to the file system example, except that this in no way needs to reflect the place in which the images are stored or organized on the drive and is really only intended to have a certain number of levels. See the image below:

Gallery Example's Structure

Notice that in my image, all of the images are contained within composites on the “Gallery” level. This is by no means necessary, nor will it be enforced in the code, but it is ideally how the images are to be organized.

Each of the objects in the hierarchy needs to implement a certain interface. Since JavaScript does not have interfaces we just need to make sure we implement certain methods. Below is the list of methods and what they do:

Composite-Specific Methods
add add a child node to this composite
remove remove a child node from this composite (or one in a deeper level)
getChild returns a child object
Gallery-Specific Methods
hide hides the composite and all of its children
show shows the composite and all of its children
Helper Methods
getElement get the HTML element of the node

First I’ll show the JavaScript for implementing GalleryComposite using this interface. It may be worth noting that I will being using the jQuery JavaScript library to help me out with some of the DOM work.

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
var GalleryComposite = function (heading, id) {
this.children = [];
this.element = $('<div id="' + id + '" class="composite-gallery"></div>')
.append('<h2>' + heading + '</h2>');
}
GalleryComposite.prototype = {
add: function (child) {
this.children.push(child);
this.element.append(child.getElement());
},
remove: function (child) {
for (var node, i = 0; node = this.getChild(i); i++) {
if (node == child) {
this.children.splice(i, 1);
this.element.detach(child.getElement());
return true;
}
if (node.remove(child)) {
return true;
}
}
return false;
},
getChild: function (i) {
return this.children[i];
},
hide: function () {
for (var node, i = 0; node = this.getChild(i); i++) {
node.hide();
}
this.element.hide(0);
},
show: function () {
for (var node, i = 0; node = this.getChild(i); i++) {
node.show();
}
this.element.show(0);
},
getElement: function () {
return this.element;
}
}

Next we’ll implement the images using GalleryImage:

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
var GalleryImage = function (src, id) {
this.children = [];
this.element = $('<img />')
.attr('id', id)
.attr('src', src);
}
GalleryImage.prototype = {
// Due to this being a leaf, it doesn't use these methods,
// but must implement them to count as implementing the
// Composite interface
add: function () { },
remove: function () { },
getChild: function () { },
hide: function () {
this.element.hide(0);
},
show: function () {
this.element.show(0);
},
getElement: function () {
return this.element;
}
}

Notice that the GalleryImage class doesn’t do anything in the add, remove, and getChild functions. Since it is the leaf class, it doesn’t contain any children, so it doesn’t do anything for these methods. However, we do need to include those functions in order to comply with the Interface we set up. After all, the composite objects don’t know that it’s a leaf and might try calling those methods.

Now that the classes are set up, we’ll write a bit of JavaScript code that utilizes those classes to create and show a real gallery. Though this example only shows the use of three of the methods shown above, you could extend this code to create a dynamic gallery, where users could add and remove images and folders easily.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var container = new GalleryComposite('', 'allgalleries');
var gallery1 = new GalleryComposite('Gallery 1', 'gallery1');
var gallery2 = new GalleryComposite('Gallery 2', 'gallery2');
var image1 = new GalleryImage('image1.jpg', 'img1');
var image2 = new GalleryImage('image2.jpg', 'img2');
var image3 = new GalleryImage('image3.jpg', 'img3');
var image4 = new GalleryImage('image4.jpg', 'img4');
gallery1.add(image1);
gallery1.add(image2);
gallery2.add(image3);
gallery2.add(image4);
container.add(gallery1);
container.add(gallery2);
// Make sure to add the top container to the body,
// otherwise it'll never show up.
container.getElement().appendTo('body');
container.show();

You can demo the code on the Composite Pattern Demo Page. You can view the source code of that page to see that none of the albums or images were premade using HTML, but were all added to the DOM via our JavaScript code.

Benefits and Drawbacks of the Composite Pattern

You can see the amazing benefits of always being able to just call a function on a top level object and have the results happen to any or all of the nodes in the composite structure. The code becomes much easier to use. Also the objects in the composite are loosely coupled because they all just follow the same interface. Finally, the composite gives a nice structure to the objects, rather than keeping them all in separate variables or in an array.

However, as mentioned earlier, the composite pattern can be deceptive. Making a call to a single function may be so easy that you might not realize the adverse effect it’ll have on performance if the composite grows rather large.

The Composite pattern is a powerful tool, but as many know, “with great power comes great responsibility.” Use JavaScript’s Composite pattern wisely and you’ll be one step closer to becoming a JavaScript guru. If you thought this was helpful or you just plain liked the article, please spread the word using the social sharing buttons below the post. Thanks!

JavaScript Design Patterns series:

Author: Joe Zimmerman

Author: Joe Zimmerman Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.