The Command Pattern in JavaScript: Encapsulating Function Property Calls

The dot operator is part of JavaScript's built-in object-oriented paradigm. Like all paradigms built into any language, there will be times when the paradigm does not support required features for a particular application. We need to escape the paradigm and build the features we need. In JavaScript, building our own dot operator function is one way to make indirect object function property calls and escape the built-in paradigm. This opens the doors to having Ruby's built-in missing_method and mixins. This also allows for aspect-oriented programming. Here are a few quick examples of building a function property called send to replace the dot operator's regular property look-up chain. This encapsulation of a function property call by the send function property is also known as "The Command Pattern".

Ruby's method_missing


var myObj = {

  send: function(method) {
    var val;
    var args = [];
    for (var i=1, len=arguments.length; i<len; i++) {
      args.push(arguments[i]);
    }

    if (this[method]) {
      val = this[method].apply(args);
    } else if (this.methodMissing) {
      val = this.methodMissing(args)
    }

    return val;
  },

  foo: function() {
    return 'foo';
  },

  methodMissing: function() {
    return 'method missing';
  }
}

myObj.send('foo'); // --> 'foo'
myObj.send('bar'); // --> 'method missing'

Ruby's mixins


var alpha = {

  foo: function() {
    return "alpha's foo";
  }      

};

var beta = {

  bar: function() {
    return "beta's bar";
  }

};

var myObj = {

  include: function(mixin) {
    this.mixins = this.mixins || [];
    this.mixins.push(mixin);
  },

  send: function(method) {
    if (this[method]) {
      return this[method]();
    }

    for (var i=0, len=this.mixins.length; i<len; i++) {
      var mixin = this.mixins[i];
      if (mixin[method]) {
        return mixin[method]();
      }
    }

  },

  foo: function() {
    return "myObj's foo";
  }

};

myObj.include(beta);
myObj.include(alpha);

myObj.send('foo'); // --> "myObj's foo" 
myObj.send('bar'); // --> "beta's bar" 

Aspect-oriented programming


var myObj = {

  addBeforeFilter: function(method, filter) {
    this.beforeFilters = this.beforeFilters || {};
    this.beforeFilters[method] = this.beforeFilters[method] || [];
    this.beforeFilters[method].push(filter);
  },

  addAfterFilter: function(method, filter) {
    this.afterFilters = this.afterFilters || {};
    this.afterFilters[method] = this.afterFilters[method] || [];
    this.afterFilters[method].push(filter);
  },

  send: function(method) {
    var val;

    if (this.beforeFilters[method]) {
      for (var i=0, len=this.beforeFilters[method].length; i<len; i++) {
        this.beforeFilters[method][i]();
      }
    }

    val = this[method]();

    if (this.afterFilters[method]) {
      for (var i=0, len=this.afterFilters[method].length; i<len; i++) {
        this.afterFilters[method][i]();
      }
    }

    return val;
  },

  foo: function() {
    alert('foo');
  }

};

myObj.addBeforeFilter('foo', function(){alert('before foo');});
myObj.addAfterFilter('foo', function(){alert('after foo');});

myObj.send('foo'); // three alerts "before foo", "foo", "after foo" 

A New OOP Paradigm for JavaScript

The following is proof of principle that JavaScript's built-in prototype based OOP paradigm is not the only OOP paradigm you can use in JavaScript. The following block of code defines a new OOP paradigm. This paradigm is not a replica of but is inspired by the Ruby OOP features. These include inheritance, mixins, super and propertyMissing. This new system could be extended to include aspect-oriented programming and other options because the new system controls the property look-up chain.


  var classes = {};

  function defClass(klass, constructor) {
    classes[klass] = {constructor: constructor,
                      mixins: [],
                      instanceProperties: {}
                     };
  }

  function defInstanceProperty(klass, property, value) {
    classes[klass].instanceProperties[property] = value;
  }

  function mixin(klass, mixinKlass) {
    classes[klass].mixins.push(mixinKlass);
  }

  function extend(klass, parentKlass) {
    classes[klass].parentKlass = parentKlass;
  }

  function instantiate(klass) {
    var object = {type:klass};
    var args = [object];
    for (var i=1, len=arguments.length; i<len; i++) {
      args.push(arguments[i]);
    }
    classes[klass].constructor.apply(null, args);
    return object;
  }

  function setInstanceProperty(object, variable, val) {
    object[variable] = val;
  }

  function klassSend(klass, object, property, args) {
    var instanceProperties = classes[klass].instanceProperties;
    if (instanceProperties[property] && 
        instanceProperties.hasOwnProperty(property)) {
          if (typeof instanceProperties[property] == 'function') {
            return instanceProperties[property](object, args);
          } else {
            return instanceProperties[property];
          }
    } else if (klass == object.type &&
               object.hasOwnProperty(property)) {
      return object[property];
    }

    var val = superKlassSend(klass, object, property, args);
    return val;
  }

  function superKlassSend(klass, object, property, args) {
    for (var i=0, len=classes[klass].mixins.length; i<len; i++) {
      var mixinKlass = classes[klass].mixins[i];
      try {
        return klassSend(mixinKlass, object, property, args); 
      } catch(e) {}
    }
    if (classes[klass].parentKlass) {
      try {
        return klassSend(classes[klass].parentKlass, object, property, args);
      } catch (e) {}
    }
    throw "property not found";
  }

  function superSend(klass, object, property) {
    var args = [];
    for (var i=3, len=arguments.length; i<len; i++) {
      args.push(arguments[i]);
    }

    try {
      return superKlassSend(klass, object, property, args);
    } catch (e) {
      return superKlassSend(klass, object, 'propertyMissing', args);
    }
  }

  function superConstructorSend(klass, object) {
    var args = [object];
    for (var i=2, len=arguments.length; i<len; i++) {
      args.push(arguments[i]);
    }
    classes[classes[klass].parentKlass].constructor.apply(null, args);
  }

  function send(object, property) {
     var args = [],
         result;
     for (var i=2, len=arguments.length; i<len; i++) {
       args.push(arguments[i]);
     }
     try {
       return klassSend(object.type, object, property, args);
     } catch (e) {
       return klassSend(object.type, object, 'propertyMissing', args)
     }
  }

Now we use this new OOP paradigm. Notice that below we don't use any of the usual JavaScript OOP keywords or sugar: "this", '.', '[]', 'new'. Because we are free from the built in paradigm we can use the features of the new OOP paradigm: inheritance, mixins, super, and propertyMissing.


  defClass('Person', function(self, first, last) {
    setInstanceProperty(self, 'first', first);
    setInstanceProperty(self, 'last', last);
  });
  defInstanceProperty('Person', 'toString', function(self) {
    return send(self, 'first') + ' ' + send(self, 'last');
  });
  defInstanceProperty('Person', 'propertyMissing', function(self) {
    return 'property missing here';
  });
  defInstanceProperty('Person', 'age', 29); // by default people's ages are 29

  defClass('Employee', function(self, first, last, id) {
    superConstructorSend('Employee', self, first, last);
    setInstanceProperty(self, 'id', id);
  });
  extend('Employee', 'Person');
  defInstanceProperty('Employee', 'toString', function(self) {
    return superSend('Employee', self, 'toString') + ' ' + send(self, 'id');
  });

  defClass('Manager', function(self, first, last, id, department) {
    superConstructorSend('Manager', self, first, last, id);
    setInstanceProperty(self, 'department', department);
  });
  extend('Manager', 'Employee');
  defInstanceProperty('Manager', 'toString', function(self) {
    return superSend('Manager', self, 'toString') + ' ' + send(self, 'department');
  });

  var ted = instantiate('Person', 'Ted', 'Henry');
  send(ted, 'toString'); // Ted Henry
  send(ted, 'first');      // Ted

  var frank = instantiate('Manager', 'Frank', 'Miller', 21, 'Accounts');
  send(frank, 'toString'); // Frank Miller 21 Accounts

  defClass('TheMixin');
  mixin('Employee', 'TheMixin');
  defInstanceProperty('TheMixin', 'toString', function(self) {
    return 'the mixin disrupter';
  });
  defInstanceProperty('TheMixin', 'foo', function(self) {
    return 'the foo';
  });
  defInstanceProperty('TheMixin', 'bar', 'greep');

  send(frank, 'toString'); // the mixin disrupter 21 Accounts
  send(frank, 'foo');       // the foo
  send(frank, 'foosdf');   // property missing here
  send(frank, 'age');      // 29

  setInstanceProperty(frank, 'age', 33);

  send(frank, 'age'); // 33
  send(ted, 'age');    // 29
  send(frank, 'bar');  // greep

This OOP paradigm's syntax is all function calls. Some might say it is not as pretty as the built-in prototype-based paradigm but it is not possible to add syntactic sugar to the language. If an OOP system with other features is required then falling back on the flexibility of function calls, as this example does, is certainly a powerful technique.

Comments

Have something to write? Comment on this article.

Druckerei July 4, 2007

Thanks for the all functions... it will defintly help me in the future...

thanks to all

doru August 16, 2007

you could look at the qooxdoo 0.7 OOP model, it is similar to yours.

Gavin Doughtie August 18, 2007

The Dojo Toolkit (http://www.dojotoolkit.org) has pretty deep support for AOP.

Have something to write? Comment on this article.