CHAPTER 6

image

Design Patterns: Structural

In this chapter, we will continue our look at design pattern, focusing on structural design patterns. Where the creational design patterns we looked at in the previous chapter center on object creation, structural design patterns help you combine objects together into a larger, more structured code base. They are flexible, maintainable, extensible, and ensure that if one part of your system changes, you do not need to completely rewrite the rest to suit. Structural design patterns can also be used to help interface with other code structures that you need to work together easily with in your application. Let’s look together at eight structural design patterns you may find useful in your code, together with examples.

The Adapter Pattern

The adapter pattern is a useful design pattern that can be used when you need to connect together two or more code components that otherwise wouldn’t normally connect together; similarly, it comes in useful when an API you have developed is updated such that it is no longer called in the same way—an adapter is provided that interfaces between the old and new versions, helping migration for users of your API who can take advantage of other improvements in your code without breaking theirs. The example shown in Listing 6-1 shows how to use this pattern to create an adapter for your code to map a new API interface to an older one.

Listing 6-1. The adapter pattern

// Imagine the following interface exists deep in your large code base for making Ajax requests
// over HTTP
var http = {
    makeRequest: function(type, url, callback, data) {
        var xhr = new XMLHttpRequest(),
            STATE_LOADED = 4,
            STATUS_OK = 200;

        xhr.onreadystatechange = function() {
            if (xhr.readyState !== STATE_LOADED) {
                return;
            }

            if (xhr.status === STATUS_OK) {
                callback(xhr.responseText);
            }
        };

        xhr.open(type.toUpperCase(), url);
        xhr.send(data);
    }
};

// The http.makeRequest() method defined above could be called as follows, for getting and
// updating user data in a system for a user with an ID of "12345":
http.makeRequest("get", "/user/12345", function(response) {
    alert("HTTP GET response received. User data: " + response);
});

http.makeRequest("post", "/user/12345", function(response) {
    alert("HTTP POST response received. New user data: " + response);
}, "company=AKQA&name=Den%20Odell");

// Now imagine in a refactor of your project, you decide to introduce a new structure using a
// namespace and splitting out the makeRequest() method into separate methods for HTTP GET
// and POST requests
var myProject = {
    data: {
        ajax: (function() {
            function createRequestObj(callback) {
                var xhr = new XMLHttpRequest(),
                    STATE_LOADED = 4,
                    STATUS_OK = 200;

                xhr.onreadystatechange = function() {
                    if (xhr.readyState !== STATE_LOADED) {
                        return;
                    }

                    if (xhr.status === STATUS_OK) {
                        callback(xhr.responseText);
                    }
                };

                return xhr;
            }

            return {
                get: function(url, callback) {
                    var requestObj = createRequestObj(callback);

                    requestObj.open("GET", url);
                    requestObj.send();
                },

                post: function(url, data, callback) {
                    var requestObj = createRequestObj(callback);

                    requestObj.open("POST", url);
                    requestObj.send(data);
                }
            };
        }())
    }
};

// These new get() and post() methods could be called as follows:
myProject.data.ajax.get("/user/12345", function(response) {
    alert("Refactored HTTP GET response received. User data: " + response);
});

myProject.data.ajax.post("/user/12345", "company=AKQA&name=Den%20Odell", function(response) {
    alert("Refactored HTTP POST response received. New user data: " + response);
});

// To avoid rewriting every call to the http.makeRequest() method in the rest of your code
// base, you could create an adapter to map the old interface to the new methods. The adapter
// needs to take the same input parameters as the original method it is designed to replace,
// and calls the new methods internally instead
function httpToAjaxAdapter(type, url, callback, data) {
    if (type.toLowerCase() === "get") {
        myProject.data.ajax.get(url, callback);
    } else if (type.toLowerCase() === "post") {
        myProject.data.ajax.post(url, data, callback);
    }
}

// Finaly, apply the adapter to replace the original method. It will then map the old
// interface to the new one without needing to rewrite the rest of your code at the same time
http.makeRequest = httpToAjaxAdapter;

// Use the new adapter in the same way as the original method - internally it will call the
// newer code, but externally it will appear identical to the old makeRequest() method
http.makeRequest("get", "/user/12345", function(response) {
    alert("Adapter HTTP GET response received. User data: " + response);
});

http.makeRequest("post", "/user/12345", function(response) {
    alert("Adapter HTTP POST response received. New user data: " + response);
}, "company=AKQA&name=Den%20Odell");

The adapter pattern is best used when you need to connect together code that wouldn’t otherwise go together, for example, when an external API has been updated—you create an adapter to map the new methods to the old methods to avoid needing to make changes to the rest of your code that relies on these methods.

To read more about the adapter pattern online, look at the following resources:

The Composite Pattern

The composite pattern creates an interface for one or more objects without the end user needing to know how many objects they are dealing with. It comes in useful when you want to simplify the way others access your functions; there need be no difference whether they pass a single object or an array of objects to the same method. Listing 6-2 shows a simple example of the composite pattern, allowing the user to add class names to one or more DOM nodes without needing to know whether they need to pass a single or multiple DOM nodes to the method.

Listing 6-2. The composite pattern

// Define a singleton containing methods to get references to page elements and to add
// class names to those elements
var elements = {

    // Define a method to get DOM elements by tag name. If one element is found, it is
    // returned as an individual node, or multiple elements are found, an array of those
    // found elements are returned
    get: function(tag) {
        var elems = document.getElementsByTagName(tag),
            elemsIndex = 0,
            elemsLength = elems.length,
            output = [];

        // Convert the found elements structure into a standard array
        for (; elemsIndex < elemsLength; elemsIndex++) {
            output.push(elems[elemsIndex]);
        }

        // If one element is found, return that single element, otherwise return the array
        // of found elements
        return output.length === 1 ? output[0] : output;
    },

    // Define a composite method which adds an class name to one or more elements, regardless
    // of how many are passed when it is executed
    addClass: function(elems, newClassName) {
        var elemIndex = 0,
            elemLength = elems.length,
            elem;

        // Determine if the elements passed in are an array or a single object
        if (Object.prototype.toString.call(elems) === "[object Array]") {

            // If they are an array, loop through each elements and add the class name to each
            for (; elemIndex < elemLength; elemIndex++) {
                elem = elems[elemIndex];
                elem.className += (elem.className === "" ? "" : " ") + newClassName;
            }
        } else {

            // If a single element was passed in, add the class name value to it
            elems.className += (elems.className === "" ? "" : " ") + newClassName;
        }
    }
};

// Use the elements.get() method to locate the single <body> element on the current page, and
// potentially numerous <a> elements
var body = elements.get("body"),
    links = elements.get("a");

// The composite elements.addClass() method gives the same interface to single elements
// as it does to multiple elements, simplifying its use considerably
elements.addClass(body, "has-js");
elements.addClass(links, "custom-link");

The composite pattern is best used when you do not want the developer interacting with your methods to have to worry how many objects they pass as parameters to them, thus simplifying method calls.

To read more about the composite pattern online, check out the following resources:

The Decorator Pattern

The decorator pattern acts as a way to extend and customize methods and properties of an object created from a “class” without the need for creating large numbers of subclasses that could become unmanageable. This is achieved by effectively wrapping the object in another that implements the same public methods with the relevant methods overridden according to the behavior that we are trying to augment. The code in Listing 6-3 shows an example that creates several decorators, which each augment an existing object with extra properties and behaviors.

Listing 6-3. The decorator pattern

var FormField = function(type, displayText){
    this.type = type || "text";
    this.displayText = displayText || "";
};

FormField.prototype = {
    createElement: function() {
        this.element = document.createElement("input");
        this.element.setAttribute("type", this.type);
        this.element.setAttribute("placeholder", this.displayText);
        return this.element;
    },

    isValid: function() {
        return this.element.value !== "";
    }
};

// The form field deocorator, which implements the same public methods as FormField
var FormFieldDecorator = function(formField) {
    this.formField = formField;
};

FormFieldDecorator.prototype = {
    createElement: function() {
        this.formField.createElement();
    },

    isValid: function() {
        return this.formField.isValid();
    }
};

var MaxLengthFieldDecorator = function(formField, maxLength) {
    FormFieldDecorator.call(this, formField);
    this.maxLength = maxLength || 100;
};
MaxLengthFieldDecorator.prototype = new FormFieldDecorator();
MaxLengthFieldDecorator.prototype.createElement = function() {
    var element = this.formField.createElement();
    element.setAttribute("maxlength", this.maxLength);
    return element;
};

var AutoCompleteFieldDecorator = function(formField, autocomplete) {
    FormFieldDecorator.call(this, formField);
    this.autocomplete = autocomplete || "on";
};
AutoCompleteFieldDecorator.prototype = new FormFieldDecorator();
AutoCompleteFieldDecorator.prototype.createElement = function() {
    var element = this.formField.createElement();
    element.setAttribute("autocomplete", this.autocomplete);
    return element;
};

The decorators created in Listing 6-3 can then be used as shown in Listing 6-4 to generate an object representing a form field in a form, augmenting its properties and behavior using these decorators rather than through subclasses.

Listing 6-4. The decorator pattern in use

// Create an empty <form> tag and a new FormField object to represent 
// a <input type="search"> field
var form = document.createElement("form"),
    formField = new FormField("search", "Enter your search term");

// Extend the formField object using our decorators to add maxlength and autocomplete properties
// to the resulting form field element. Note how we pass the extended formField object into each
// decorator in turn, which extends it further.
formField = new MaxLengthFieldDecorator(formField, 255);
formField = new AutoCompleteFieldDecorator(formField, "off");

// Create the HTML form field element and add it to the <form> element
form.appendChild(formField.createElement());

// Add an event handler to the <form> tag's submit event, preventing the form from submitting if
// the form field we added contains no value
form.addEventListener("submit", function(e) {

    // Stop the form from submitting
    e.preventDefault();

    // Test to see if our form field is valid, i.e. that it contains a value
    if (formField.isValid()) {

        // If it does, go ahead and submit the form
        form.submit();
    } else {

        // If it doesn't, alert the user that something is wrong and they need to correct it
        alert("Please correct the issues in the form field.");
    }
}, false);

// Add the <form> field to the current page once it has loaded
window.addEventListener("load", function() {
    document.body.appendChild(form);
}, false);

The decorator pattern is best used when you need to quickly and simply augment the behavior of object instances created from a “class” without having to resort to creating a long series of inherited subclasses from it. To read more about the decorator pattern online, check out the following resources:

The Façade Pattern

The façade pattern is very common; it is simply the act of writing a single function to simplify access to a larger and potentially more complex function or functions. It could be argued that any function that simply calls another function is an example of this pattern, but I find it best to think of it in terms of simplifying something that would otherwise take multiple steps, or to provide a single point of access to a much larger system that would make access to that system a lot easier for other developers. The code in Listing 6-5 demonstrates a simple façade that provides a wrapper to simplify cross-browser Ajax calls.

Listing 6-5. The façade pattern

// Define a function which acts as a façade to simplify and facilitate cross-browser Ajax calls,
// supporting browsers all the way back to Internet Explorer 5
function ajaxCall(type, url, callback, data) {

    // Get a reference to an Ajax connection object relevant to the current browser
    var xhr = (function() {
            try {

                // The standard method, used in all modern browsers
                return new XMLHttpRequest();
            }
            catch(e) {}

            // Older versions of Internet Explorer utilise an ActiveX object installed on the
            // user's machine
            try {
                return new ActiveXObject("Msxml2.XMLHTTP.6.0");
            }
            catch(e) {}

            try {
                return new ActiveXObject("Msxml2.XMLHTTP.3.0");
            }
            catch(e) {}

            try {
                return new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch(e) {}

            // If no relevant Ajax connection object can be found, throw an error
            throw new Error("Ajax not supported in this browser.");
        }()),
        STATE_LOADED = 4,
        STATUS_OK = 200;

    // Execute the given callback method once a succesful response is received from the server
    xhr.onreadystatechange = function() {
        if (xhr.readyState !== STATE_LOADED) {
            return;
        }

        if (xhr.status === STATUS_OK) {
            callback(xhr.responseText);
        }
    };

    // Use the browser's Ajax connection object to make the relevant call to the given URL
    xhr.open(type.toUpperCase(), url);
    xhr.send(data);
}

The façade pattern from Listing 6-5 can then be used in your code as shown in Listing 6-6, disguising the complexity behind the cross-browser Ajax operation.

Listing 6-6. The façade pattern in use

// The ajaxCall() facade function can make cross-browser Ajax calls as follows
ajaxCall("get", "/user/12345", function(response) {
    alert("HTTP GET response received. User data: " + response);
});

ajaxCall("post", "/user/12345", function(response) {
    alert("HTTP POST response received. New user data: " + response);
}, "company=AKQA&name=Den%20Odell");

The façade pattern is best used when you wish to provide access to a series of function or method calls through a single function or method in order to simplify the rest of your code base, making it easier to follow and therefore more maintainable and scalable for the future. To read more about the façade pattern online, look at the following resources:

The Flyweight Pattern

The flyweight pattern is an optimization pattern; it is useful for code that creates a large number of similar objects that would otherwise consume a large amount of memory. It replaces this multitude of similar objects with a few shared objects making the code lighter and more performant; hence the name, which heralds from the boxing world where it refers to those competitors of the lightest weight class, those who are most nimble. Listing 6-7 shows an example of a problem that the flyweight pattern is designed to address, the inefficient storage of objects.

Listing 6-7. Inefficient object instances

// Create a "class" to store data to related to employees working for one or more different
// companies
function Employee(data) {

    // Represent an employee's ID within an organisation
    this.employeeId = data.employeeId || 0;

    // Represent an employee's social security number
    this.ssId = data.ssId || "0000-000-0000";

    // Represent an employee's name
    this.name = data.name || "";

    // Represent an employee's occupation
    this.occupation = data.occupation || "";

    // Represent an employee's company name, address and country
    this.companyName = data.companyName || "";
    this.companyAddress = data.companyAddress || "";
    this.companyCountry = data.companyCountry || "";
}

// Create three methods to get the employee's name, occupation and company details from the
// stored object
Employee.prototype.getName = function() {
    return this.name;
};

Employee.prototype.getOccupation = function() {
    return this.occupation;
};

Employee.prototype.getCompany = function() {
    return [this.companyName, this.companyAddress, this.companyCountry].join(", ");
};

// Create four employee objects - note that two share the same company information, and two
// share the same ssId and name. As more objects are created, the amount of data repeated will
// grow, consuming more memory due to inefficiency
var denOdell = new Employee({
        employeeId: 1456,
        ssId: "1234-567-8901",
        name: "Den Odell",
        occupation: "Head of Web Development",
        companyName: "AKQA",
        companyAddress: "1 St. John's Lane, London",
        companyCountry: "GB"
    }),
    steveBallmer = new Employee({
        employeeId: 3,
        ssId: "8376-940-1673",
        name: "Steve Ballmer",
        occupation: "Ex-CEO",
        companyName: "Microsoft",
        companyAddress: "1 Microsoft Way, Redmond, WA",
        companyCountry: "US"
    }),
    billGates = new Employee({
        employeeId: 1,
        ssId: "7754-342-7584",
        name: "Bill Gates",
        occupation: "Founder",
        companyName: "Microsoft",
        companyAddress: "1 Microsoft Way, Redmond, WA",
        companyCountry: "US"
    }),
    billGatesPhilanthropist = new Employee({
        employeeId: 2,
        ssId: "7754-342-7584",
        name: "Bill Gates",
        occupation: "Philanthropist",
        companyName: "Gates Foundation",
        companyAddress: "500 Fifth Avenue North, Seattle, WA",
        companyCountry: "US"
    });

The flyweight pattern is applied by attempting to deconstruct an existing “class” such that any data that might be repeated between object instances is minimized. This is achieved by studying any current object instances for repetitive data and creating separate “classes” to represent that data. A single object instance can then represent the repeated data, which can be refered to from multiple object instances of the original “class,” resulting in less data being stored and thus reducing the memory footprint of the application.

Any data core to each current object instance is known as the intrinsic data of that “class,” and any data that can be extracted, stored separately, and refered to from the object instead is known as its extrinsic data. In Listing 6-7, the intrinsic data pertaining to an employee—essentially that which is unique—are its employeeId and its occupation values. The company data, which is currently duplicated on multiple Employee objects, can be extracted and stored separately; so can each individual person’s data such as their name and ssId values. An employee can therefore be represented with four properties: employeeId, occupation, company, person. The last two properties reference other object instances.

The flyweight pattern is applied in three stages, as shown in Listing 6-8: first, by creating new “classes” to represent extrinsic data; second, by applying the factory pattern to ensure that objects previously created are not recreated; and, finally, by writing code to enable the creation of objects in the same way as originally, allowing all of the flyweight’s heavy lifting to occur behind the scenes.

Listing 6-8. The flyweight pattern

// The first stage of applying the flyweight pattern is to extract intrinsic data from
// extrinsic data in the objects we wish to make more memory-efficient
//
// There are two sets of extrinsic data in an Employee object from Listing 6-7 - people data
// and company data. Let's create two "classes" to represent those types of data
//
// A Person object represents an individual's social security number and their name
function Person(data) {
    this.ssId = data.ssId || "";
    this.name = data.name || "";
}

// A Company object represents a company's name, address and country details
function Company(data) {
    this.name = data.name || "";
    this.address = data.address || "";
    this.country = data.country || "";
}

// The second stage of the flyweight pattern is to ensure any objects representing unique
// extrinsic data are only created once and stored for use in future. This is achieved by
// harnessing the factory pattern for each of the new extrinsic data "classes" to abstract
// away the creation of the object instance so that if a previously-existing object is found,
// that can be returned instead of creating a new instance
var personFactory = (function() {

        // Create a variable to store all instances of the People "class" by their ssId
        var people = {},
            personCount = 0;

        return {

            // Provide a method to create an instance of the People "class" if one does not
            // already exist by the given ssId provided in the data input. If one exists,
            // return that object rather than creating a new one
            createPerson: function(data) {
                var person = people[data.ssId],
                    newPerson;

                // If the person by the given ssId exists in our local data store, return their
                // object instance, otherwise create a new one using the provided data
                if (person) {
                    return person;
                } else {
                    newPerson = new Person(data);
                    people[newPerson.ssId] = newPerson;
                    personCount++;

                    return newPerson;
                }
            },

            // Provide a method to let us know how many Person objects have been created
            getPersonCount: function() {
                return personCount;
            }
        };
    }()),

    // Create a similar factory for Company objects, storing company data by name
    companyFactory = (function() {
        var companies = {},
            companyCount = 0;

        return {
            createCompany: function(data) {
                var company = companies[data.name],
                    newCompany;

                if (company) {
                    return company;
                } else {
                    newCompany = new Company(data);
                    companies[newCompany.name] = newCompany;
                    companyCount++;

                    return newCompany;
                }
            },

            getCompanyCount: function() {
                return companyCount;
            }
        };
    }()),

    // The third stage of the flyweight pattern is to allow the creation of objects in a
    // simliar way to that in Listing 6-7, providing all the handling of data storage in the
    // most efficient way in a transparent way to the end user
    //
    // Create an object with methods to store employee data and to return data from each
    // object by their employeeId. This simplifies the end user's code as they do not need to
    // access methods on underlying objects directly, they only need interface with this handler
    employee = (function() {

        // Create a data store for all employee objects created
        var employees = {},
            employeeCount = 0;

        return {

            // Provide a method to add employees to the data store, passing the provided data
            // to the Person and Company factories and storing the resulting object, consisting
            // of the enployeeId, occupation, person object reference, and company object
            // reference in the local data store
            add: function(data) {

                // Create or locate Person or Company objects that correspond to the provided
                // data, as appropriate
                var person = personFactory.createPerson({
                        ssId: data.ssId,
                        name: data.name
                    }),
                    company = companyFactory.createCompany({
                        name: data.companyName,
                        address: data.companyAddress,
                        country: data.companyCountry
                    });

                // Store a new object in the local data store, containing the employeeId,
                // their occupation, and references to the company they work for and their
                // unique personal data, including their name and social security number
                employees[data.employeeId] = {
                    employeeId: data.employeeId,
                    occupation: data.occupation,
                    person: person,
                    company: company
                };

                employeeCount++;
            },

            // Provide a method to return the name of an employee by their employeeId - the
            // data is looked up from the associated Person object
            getName: function(employeeId) {
                return employees[employeeId].person.name;
            },

            // Provide a method to return the occupation of an employee by their employeeId
            getOccupation: function(employeeId) {
                return employees[employeeId].occupation;
            },

            // Provide a method to return the address of the company an employee works for -
            // the data is looked up from the associated Company object
            getCountry: function(employeeId) {
                var company = employees[employeeId].company;
                return [company.name, company.address, company.country].join(", ");
            },

            // Provide a utlility method to tell us how many employees have been created
            getTotalCount: function() {
                return employeeCount;
            }
        };
    }());

The flyweight code from Listing 6-8 can then be used as shown in Listing 6-9, which replicates the behavior of Listing 6-7. The more repeated data in the original memory-consuming objects the flyweight pattern is applied to, the more objects will become shared, therefore reducing the memory footprint of the application, proving the usefulness of this design pattern.

Listing 6-9. The flyweight pattern in use

// Create four employee objects - note that two share the same company information, and two
// share the same ssId and name. Behind the scenes, the flyweight pattern from Listing 6-8
// ensures that repeated person and company data is stored in the most efficient way possible.
var denOdell = employee.add({
        employeeId: 1456,
        ssId: "1234-567-8901",
        name: "Den Odell",
        occupation: "Head of Web Development",
        companyName: "AKQA",
        companyAddress: "1 St. John's Lane, London",
        companyCountry: "GB"
    }),
    steveBallmer = employee.add({
        employeeId: 3,
        ssId: "8376-940-1673",
        name: "Steve Ballmer",
        occupation: "Ex-CEO",
        companyName: "Microsoft",
        companyAddress: "1 Microsoft Way, Redmond, WA",
        companyCountry: "US"
    }),
    billGates = employee.add({
        employeeId: 1,
        ssId: "7754-342-7584",
        name: "Bill Gates",
        occupation: "Founder",
        companyName: "Microsoft",
        companyAddress: "1 Microsoft Way, Redmond, WA",
        companyCountry: "US"
    }),
    billGatesPhilanthropist = employee.add({
        employeeId: 2,
        ssId: "7754-342-7584",
        name: "Bill Gates",
        occupation: "Philanthropist",
        companyName: "Gates Foundation",
        companyAddress: "500 Fifth Avenue North, Seattle, WA",
        companyCountry: "US"
    });

// We've created three objects representing people by ssId and name - Den Odell, Steve Ballmer
// and Bill Gates
alert(personFactory.getPersonCount()); // 3

// We've created three objects representing companies by name, address and country - AKQA,
// Microsoft and the Gates Foundation
alert(companyFactory.getCompanyCount()); // 3

// We've created four objects representing employees, with two unique properties and two
// properties linking to existing person and company objects. The more employee objects we
// create with shared person and company data, the less data we're storing in our application
// and the more effective the flyweight pattern becomes
alert(employee.getTotalCount()); // 4

The flyweight pattern is best used when you have a large number of objects with similar shared property name-value pairs that could be separated into smaller objects with data shared by reference between them in order to make the memory footprint of your code lighter and your code more efficient. To read more about the flyweight pattern online, check out the following resources:

The Mixin Pattern

The mixin pattern avoids the need for extensive subclassing and inheritance chains by quickly and easily applying a set of methods and properties from one object directly to another, or directly to the prototype of a “class” such that all object instances have access to those properties and methods. Although that may sound like a “hack,” particularly for those developers approaching JavaScript from a traditional object-oriented background, this pattern plays directly to the strengths of the JavaScript language and its use of prototypes rather than the strict classical inheritance applied by other languages and, provided that it is used carefully, can simplify development and code maintenance. The code in Listing 6-10 shows how to use the mixin pattern to simply and quickly apply a set of common methods to a number of objects.

Listing 6-10. The mixin pattern

// Define a mixin which enables debug logging, to be applied to any object or "class"
var loggingMixin = {

        // Define a storage array for logs
        logs: [],

        // Define a method to store a message in the log
        log: function(message) {
            this.logs.push(message);
        },

        // Define a method to read out the stored logs
        readLog: function() {
            return this.logs.join(" ");
        }
    },
    element,
    header,
    textField,
    emailField;

// Function to apply methods and properties from one object to another, which we'll use to apply
// the mixin to other objects
function extendObj(obj1, obj2) {
    var obj2Key;

    for (obj2Key in obj2) {
        if (obj2.hasOwnProperty(obj2Key)) {
            obj1[obj2Key] = obj2[obj2Key];
        }
    }

    return obj1;
}

// Define a singleton to which we will apply the mixin, though will function fine without it
element = {
    allElements: [],

    create: function(type) {
        var elem = document.createElement(type);
        this.allElements.push(elem);

        // Use the mixin method log(), ensuring it exists first before calling it. If the mixin
        // is not applied, then the method will still function fine
        if (typeof this.log === "function") {
            this.log("Created an element of type: " + type);
        }

        return elem;
    },

    getAllElements: function() {
        return this.allElements;
    }
};

// Define a simple "class" to which we will apply the mixin
function Field(type, displayText) {
    this.type = type || "";
    this.displayText = displayText || "";

    // Ensure the mixin method log() exists before executing
    if (typeof this.log === "function") {
        this.log("Created an instance of Field");
    }
}

Field.prototype = {
    getElement: function() {
        var field = document.createElement("input");
        field.setAttribute("type", this.type);
        field.setAttribute("placeholder", this.displayText);

        if (typeof this.log === "function") {
            this.log("Created a DOM element with placeholder text: " + this.displayText);
        }

        return field;
    }
};

// Apply the mixin directly to the 'element' object by essentially copying over methods and
// properties from the mixin to the singleton
element = extendObj(element, loggingMixin);

// Apply the mixin to the Field "class" prototype, making its methods available to each object
// instance created from it
Field.prototype = extendObj(Field.prototype, loggingMixin);

// Create a new DOM element using the element.create() method
header = element.create("header");

// Create two object instances, both of which receive the getElement method from the prototype
textField = new Field("text", "Enter the first line of your address");
emailField = new Field("email", "Enter your email address");

// Add the elements stored in these objects to the current page
document.body.appendChild(textField.getElement());
document.body.appendChild(emailField.getElement());

// Output the logs stored via the mixin
alert(loggingMixin.readLog());

// Outputs the following - note how all the logs from each usage of the mixin are
// stored together:
/*
Created an element of type: header
Created an instance of Field
Created an instance of Field
Created a DOM element with placeholder text: Enter the first line of your address
Created a DOM element with placeholder text: Enter your email address
*/

If you study the code in Listing 6-10, you may notice something unexpected: despite applying the mixin independently to the singleton and the “class,” all of the logged data is stored together. Calling the readLog() method on any object containing that method will output the same result. This happens because when the extendObj() function copies objectlike properties from one object to another, such as the logs array in this example (remember that an array is a type of object in JavaScript), these are copied by reference and are not actual data duplicates. Each time this property is accessed from any object, the same property is used, the original from the loggingMixin object. In the case of this example, we want to see all of the logs together so this is useful; however, this may not be the result you require when using this pattern in your own code. Should you wish to create separate duplicates of the properties copied over, update the extendObj() function to that shown in Listing 6-11.

Listing 6-11. Updated extendObj() function to duplicate properties rather than copying by reference

// Update extendObj() to duplicate object-based properties rather than point to them 
// by reference
function extendObj(obj1, obj2) {
    var obj2Key,
        value;

    for (obj2Key in obj2) {
        if (obj2.hasOwnProperty(obj2Key)) {
            value = obj2[obj2Key];

            // If the value being copied is an array, then copy a duplicate of that array using
            // the slice() method
            if (Object.prototype.toString.apply(value) === "[object Array]") {
                obj1[obj2Key] = value.slice();

            // Otherwise, if the value being copied in an object, and not an array, then copy
            // across a duplicate of that object using a recursive call to this function
            } else if (typeof obj2[obj2Key] === "object") {
                obj1[obj2Key] = extendObj({}, value);

            // Otherwise, copy across the value as usual
            } else {
                obj1[obj2Key] = value;
            }
        }
    }

    return obj1;
}

The mixin pattern is best used when you wish to quickly apply a set of properties and methods directly from one object to another, or to a “class” for use by all its object instances, without resorting to what would otherwise be complicated subclassing and inheritance. To read more about the mixin pattern online, look at the following resources:

The Module Pattern

The module pattern is probably the one most commonly used by professional JavaScript developers. In fact, we’ve covered the basis of the pattern twice in previous chapters already: first when discussing public, private, and protected variables in Chapter 1, and again when discussing ways of improving JavaScript compression in Chapter 4. It’s all based around the self-executing function closure, which allows us to create a sandboxed area of code that can access global variables and functions but that does not expose variables or functions declared within it to the surrounding scope, unless explicitly with a return statement. The simplest example of a self-executing function is shown here:

(function() {
    // Any variables or functions declared within this function aren't accessible outside it
}());

We can use this pattern to divide up our code base into smaller, related chunks of code, which we call modules, hence the name of the pattern. Each of these modules should clearly state their dependencies on other parts of the code, if any, which should be passed in as parameters to the function, as shown here:

(function($) {
    // We very clearly define jQuery as a dependency for this 'module', making it available
    // internally through the $ variable
}(jQuery));

Image Tip  Accessing JavaScript parameters within a function is faster than accessing a global variable in the scope outside the function, as the language interpreter does not have to perform the extra step of leaving the scope of the current function to search for variables.

The basic form of the module pattern is completed by using the return statement within the function closure to pass back any declared code that might be of use to other modules, or to the main application itself. Listing 6-12 shows the complete form of the module pattern, based on Listing 5-10 from the previous chapter.

Listing 6-12. The Module Pattern

// The module pattern is distinctive as it uses a combination of a self-executing anonymous 
// function closure, with any dependencies passed in as parameters, and an optional return
// statement which allows code created within the closure to be made available externally

// Our only dependency is the 'document' object which contains the browser's cookie data. As an
// added security measure, we can include a final listed parameter named 'undefined' to which we
// never pass a value. This ensures that the variable named 'undefined' always contains an
// undefined value provided we always ensure we never pass in a value to this parameter.
// Otherwise it might be possible for other code, whether through malicious reasons or
// otherwise, to overwrite this value as it is not a reserved word in the language causing all
// kinds of havoc to the way our code behaves.
var cookie = (function(document, undefined) {
    var allCookies = document.cookie.split(";"),
        cookies = {},
        cookiesIndex = 0,
        cookiesLength = allCookies.length,
        cookie;

    for (; cookiesIndex < cookiesLength; cookiesIndex++) {
        cookie = allCookies[cookiesIndex].split("=");

        cookies[unescape(cookie[0])] = unescape(cookie[1]);
    }

    // Return any methods, properties or values that you wish to make available to the rest of
    // your code base. In this case, the following two methods will be exposed through the
    // 'cookie' variable, creating a singleton
    return {
        get: function(name) {
            return cookies[name] || "";
        },

        set: function(name, value) {
            cookies[name] = value;
            document.cookie = escape(name) + "=" + escape(value);
        }
    };

// Pass in any dependencies at the point of function execution
}(document));

In larger code bases that utilize namespacing through singleton object structures, the module pattern is used in a slightly different way than we’ve seen; in this case, we pass in a dependency that we then return at the end of the function closure, using the module to augment the singleton with new properties and methods. Listing 6-13 shows the module pattern as it applies to the augmentation of namespaces, one of its most common uses.

Listing 6-13. Augmenting namespaces using the module pattern

// Define a namespace which we will populate with code modules
var myData = {};

// Ajax module, added to the myData namespace through augmentation
// The namespace is passed in as a parameter and, once it has been augmented with new method, is
// finally returned back, overwriting the original namespace with the new, augmented one
myData = (function(myNamespace, undefined) {

    // Add an 'ajax' object property to the namespace and populate it with related methods
    myNamespace.ajax = {
        get: function(url, callback) {
            var xhr = new XMLHttpRequest(),
                LOADED_STATE = 4,
                OK_STATUS = 200;

            xhr.onreadystatechange = function() {
                if (xhr.readyState !== LOADED_STATE) {
                    return;
                }

                if (xhr.status === OK_STATUS) {
                    callback(xhr.responseText);
                }
            };

            xhr.open("GET", url);
            xhr.send();
        }
    };

    // Return the new, augmented namespace back to the myData variable
    return myNamespace;

// We can use the following defence mecahnism, which reverts to an empty object if the myData
// namespace object does not yet exist. This is useful when you have modules split over several
// files in a large namespace and you're unsure if the namespace passed in has been initialized
// elsewhere before
}(myData || {}));

// Cookies module, added to the myData namespace through augmentation
// As before, the namespace is passed in, augmented, and then returned, overwriting the original
// namespace object. At this point, the myData namespace contains the Ajax module code
myData = (function(myNamespace, undefined) {

    // Add a 'cookies' object property to the namespace and populate it with related methods
    myNamespace.cookies = {
        get: function(name) {
            var output = "",
                escapedName = escape(name),
                start = document.cookie.indexOf(escapedName + "="),
                end = document.cookie.indexOf(";", start);

            end = end === -1 ? (document.cookie.length - 1) : end;

            if (start >=0) {
                output = document.cookie.substring(start + escapedName.length + 1, end);
            }

            return unescape(output);
        },
        set: function(name, value) {
            document.cookie = escape(name) + "=" + escape(value);
        }
    };

    return myNamespace;
}(myData || {}));

// Execute methods directly through the myData namespace object, which now contains both Ajax
// and Cookies modules
myData.ajax.get("/user/12345", function(response) {
    alert("HTTP GET response received. User data: " + response);
});
myData.cookies.set("company", "AKQA");
myData.cookies.set("name", "Den Odell");

alert(myData.cookies.get("company")); // AKQA
alert(myData.cookies.get("name"));    // Den Odell

The module pattern is best used when you wish to break up large code bases into smaller, manageable, self-contained parts, each with a clear set of dependencies and a well-defined purpose. Because of their sandboxed nature, their self-executing function blocks are also prime territory for creating smaller file sizes through obfuscation and compilation, topics that we covered in Chapter 4. In Chapter 9, we will be looking at an alternative approach to defining and loading modules into your JavaScript code using the Asynchronous Module Definition (AMD) API, but for now if you wish to read more about the module pattern online, check out the following resources:

The Proxy Pattern

The proxy pattern is one that defines a surrogate, or stand-in, object or method to replace or augment an existing object or method in order to improve its performance or add extra functionality without affecting the other parts of the code that use that object or method already. The most common way that I and a number of other professional JavaScript developers use this pattern is to wrap around an existing method or function without changing the method or function name, as shown in Listing 6-14.

Listing 6-14. The proxy pattern

// To proxy the myData.cookies.get() method from Listing 6-13, we begin by storing the current 
// method in a variable
var proxiedGet = myData.cookies.get;

// Override the get() method with a new function which proxies the original and augments its
// behavior
myData.cookies.get = function() {

    // Call the proxied (original) method to get the value it would have produced
    var value = proxiedGet.apply(this, arguments);

    // Do something with the value returned from the proxied method
    value = value.toUpperCase();

    // Return the manipulated value with the same type as the proxied method, so that the use of
    // this new method does not break any existing calls to it
    return value;
};

A variation of the proxy pattern known as the virtual proxy can be used to improve performance and memory usage by delaying object instantiation, and thus the execution of a constructor function, until the point that methods from the object instance are actually called, as demonstrated in Listing 6-15.

Listing 6-15. The virtual proxy pattern

// Define a "class" for constructing an object representing a simple form field
function FormField(type, displayText){
    this.type = type || "text";
    this.displayText = displayText || "";

    // Create and initialize a form field DOM element
    this.element = document.createElement("input");
    this.element.setAttribute("type", this.type);
    this.element.setAttribute("placeholder", this.displayText);
}

// Define two methods for object instances to inherit
FormField.prototype = {
    getElement: function() {
        return this.element;
    },

    isValid: function() {
        return this.element.value !== "";
    }
};

// Now replace the FormField "class" with a proxy that implements the same methods, yet delays
// calling the original constructor function until those methods are actually called, saving on
// memory resources and improving performance
// Optionally, use the module pattern to localise the scope of the proxy "class", passing in the
// original FormField "class" and returning the proxied version of it
FormField = (function(FormField) {

    // Define a proxy constructor, similar to the original FormField "class"
    function FormFieldProxy(type, displayText) {
        this.type = type;
        this.displayText = displayText;
    }

    FormFieldProxy.prototype = {

        // Define a property to store the reference to the object instance of the original
        // "class" once instantiated
        formField: null,

        // Define a new 'initialize' method whose task it is to create the object instance of
        // FormField if it does not already exist and execute the constructor function from the
        // original "class"
        initialize: function() {
            if (!this.formField) {
                this.formField = new FormField(this.type, this.displayText);
            }
        },

        // Proxy the original methods with new ones that call the intialize() method to
        // instantiate the FormField "class" only when one of these methods are called
        getElement: function() {
            this.initialize();
            return this.formField.getElement();
        },

        isValid: function() {
            this.initialize();
            return this.formField.isValid();
        }
    };

    // Return the proxied "class" to replace the original with
    return FormFieldProxy;
}(FormField));

// Create two object instances, both of which will actually be calling the proxy rather than the
// original "class", meaning the DOM elements will not be created at this stage, saving memory
// and improving performance
var textField = new FormField("text", "Enter the first line of your address"),
    emailField = new FormField("email", "Enter your email address");

// Add the elements stored in these objects to the current page when loaded - at this point the
// getElement() method is called, which in turn calls initialize(), creating an instance of the
// original "class" and executing its constructor function which performs the actual DOM element
// creation. This ensures the memory used to store the DOM element is only taken up at the exact
// point it is required
window.addEventListener("load", function() {
    document.body.appendChild(textField.getElement());
    document.body.appendChild(emailField.getElement());
}, false);

// Execute another method from the proxy, this time the object instance of the original "class"
// won't be recreated and the stored instance will be used instead
alert(emailField.isValid()); // false

You could extend the proxy pattern further to improve performance and reduce memory by delaying or grouping together calls, such as Ajax requests, or other network-related calls, for objects that could potentially make multiple calls around the same time.

The proxy pattern is best used when you need to override the behavior of specific methods on an object or “class,” or applied in a way to improve the performance of an existing “class” such that it is not actually instantiated until one of its methods are called. To read more about the proxy pattern online, look at the following resources:

Summary

In this chapter, we have looked at structural design patterns that you can use where appropriate to help you structure large JavaScript applications and improve their performance. These are tools in your Swiss Army knife of JavaScript development but, like all tools, you need to know when and where to use them best. Remember the old adage: “When you have a hammer, everything looks like a nail.” Familiarize yourself with the patterns in this chapter and their use cases, and ensure that you don’t use a design pattern before you recognize the need for it in your code.

In the following chapter, we will look at a number of behavioral design patterns, which can be used to simplify the communication between different objects in your JavaScript application’s code base.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.26.176