Composite pattern example

A simple example of the composite pattern.

by Richard Hunter

HTML

<h1>Composite Pattern</h1>

<p>In the composite pattern, a type is able to contain members of it's super type. This Fiddle shows a simple example of the pattern where the Super type is represented by a File and the Sub type by a Folder. In a conventional file structure a Folder is a type of file but can contain files and other folders</p>
<p>The composite pattern is useful for building tree like structures where each node should provide a common interface</p>
<p>There are two main types involved in the composite pattern: A Base type which provides an interface and common functionality; and a Composite type which can contain a number of other types, both Base and Composite.</p>
<p>In a tree like structure, instances of the Base type function as leaf nodes whilst instances of the Composite type function as ancestor nodes.</p>
<p>Composite types must maintain a reference to each of it's contained children, whilst each type which exists in a Composite type must maintain a reference to it's parent. It is advised that this relationship is set up within the composite type</p>
<p>Run the example and view the output in the console. The code uses some Javascript functionality which is only currently available in Firefox</p>
</p>

JavaScript

//  composite pattern example. (Firefox only)
var filesize1 = 24;
var filesize2 = 56;
var filesize3 = 13;

function extend(Parent, Child) {

    function F() {}
    F.prototype = Parent.prototype;
    Child.prototype = new F();

}

function File(name, filesize) {

    this.name = name;
    this.size = filesize;

}
File.prototype = {


    getName: function () {

        return this.name;
    },

    getFileSize: function () {

        return this.size;
    },

    //  demonstrates calling up parent chain for context information
    getPath: function (pathComponent) {

        pathComponent = pathComponent || "";
        pathComponent = "/" + this.getName() + pathComponent;

        if (this.parent) {
            return this.parent.getPath(pathComponent);
        } else {
            return pathComponent;
        }
    }
}


function Folder(name) {

    File.call(this, name, 0);
    this.files = [];
}

extend(File, Folder);

//  demonstrates delegating to constituent parts
Folder.prototype.getFileSize = function () {

    return this.files.reduce(function (initial, file) {

        return initial + file.getFileSize();

    }, 0);

};

Folder.prototype.addFile = function (file) {

    this.files.push(file);
    // create reference to parent
    file.parent = this;

}

Folder.prototype.addFiles = function (...files) {

    files.forEach(function (file) {

        this.addFile(file);

    }, this);

}


var client = {

    run: function () {

        var rootfolder = new Folder("root-folder");

        var folder = new Folder("my-folder");

        rootfolder.addFile(folder);

        var file1 = new File("file1", filesize1);
        var file2 = new File("file2", filesize2);
        var file3 = new File("file3", filesize3);


        folder.addFiles(file1, file2, file3);

        console.log("my new folder is called: ", folder.getName());
        console.log("folderSize is: ", folder.getFileSize());
        console.log("folderPath is: ", file1.getPath());

   ...