Template Method

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.



Usage in JavaScript:
medium low


Summary


The Template Method pattern provides an outline of a series of steps for an algorithm. Objects that implement these steps retain the original structure of the algorithm but have the option to redefine or adjust certain steps. This pattern is designed to offer extensibility to the client developer.

Template Methods are frequently used in general purpose frameworks or libraries that will be used by other developer An example is an object that fires a sequence of events in response to an action, for example a process request. The object generates a 'preprocess' event, a 'process' event and a 'postprocess' event. The developer has the option to adjust the response to immediately before the processing, during the processing and immediately after the processing.

An easy way to think of Template Method is that of an algorithm with holes (see diagram below). It is up to the developer to fill these holes with appropriate functionality for each step.


Diagram



Participants


The objects participating in this pattern are:

  • AbstractClass -- In sample code: datastore
    • offers an interface for clients to invoke the templateMethod
    • implements template method which defines the primitive Steps for an algorithm
    • provides the hooks (through method overriding) for a client developer to implement the Steps
  • ConcreteClass -- In sample code: mySql
    • implements the primitive Steps as defined in AbstractClass

JavaScript Code


This is an example where we use JavaScript's prototypal inheritance. The inherit function helps us establish the inheritance relationship by assigning a base object to the prototype of a newly created descendant object.

The datastore function represents the AbstractClass and mySql represents the ConcreteClass. mySql overrides the 3 template methods: connect, select, and disconnect with datastore-specific implementations.

The template methods allow the client to change datastore (SQL Server, Oracle, etc.) by adjusting (filling in the blanks) only the template methods. The rest, such as, the order of the steps, stays the same for any datastore.

The log function is a helper which collects and displays results.


var datastore = {
    process: function() {
        this.connect();
        this.select();
        this.disconnect();
        return true;
    }
};

function inherit(proto) {
    var F = function() { };
    F.prototype = proto;
    return new F();
}

// log helper
var log = (function() {
    var log = "";
    return {
        add: function(msg) { log += msg + "\n"; },
        show: function() { alert(log); log = ""; }
    }
})();


function run() {

    var mySql = inherit(datastore);

    // implement template steps

    mySql.connect = function() {
        log.add("MySQL: connect step");
    };
    mySql.select = function() {
        log.add("MySQL: select step");
    };
    mySql.disconnect = function() {
        log.add("MySQL: disconnect step");
    };

    mySql.process();

    log.show();
}
Run



JavaScript Optimized Code


The Namespace pattern is applied to keep the code out of the global namespace. Our namespace is named Patterns.Classic. A Revealing Module named Template returns (i.e. reveals) a single item: the datastore object.

A second namespace is created named Patterns.Utils which holds utility-type functions. A Revealing Module named Common returns (i.e. reveals) two items: inherit and our trusted log utility. With this we have limited our footprint on the global namespace to a single item, i.e. the Patterns root of our namespace.

The Patterns object contains the namespace function which constructs namespaces non-destructively, that is, if a name already exists it won't overwrite it.

The log function is a helper which collects and displays results.


var Patterns = {
    namespace: function (name) {
        var parts = name.split(".");
        var ns = this;

        for (var i = 0, len = parts.length; i < len; i++) {
            ns[parts[i]] = ns[parts[i]] || {};
            ns = ns[parts[i]];
        }

        return ns;
    }
};

Patterns.namespace("Classic").Template = (function () {
    var datastore = {
        process: function () {
            this.connect();
            this.select();
            this.disconnect();
            return true;
        }
    };

    return { datastore: datastore };

})();

Patterns.namespace("Utils").Common = (function () {

    var inherit = function (proto) {
        var F = function () { };
        F.prototype = proto;
        return new F();
    };

    // log helper
    var log = (function () {
        var log = "";
        return {
            add: function (msg) { log += msg + "\n"; },
            show: function () { alert(log); log = ""; }
        }
    })();

    return {
        inherit: inherit,
        log: log
    };

})();

function run() {

    var utils = Patterns.Utils.Common;

    var store = Patterns.Classic.Template.datastore;
    var mySql = utils.inherit(store);

    // implement template steps

    mySql.connect = function () {
        utils.log.add("MySQL: connect step");
    };
    mySql.select = function () {
        utils.log.add("MySQL: select step");
    };
    mySql.disconnect = function () {
        utils.log.add("MySQL: disconnect step");
    };

    mySql.process();

    utils.log.show();
}
Run



  Strategy
Visitor