OOP The Good Parts: Message Passing, Duck Typing, Object Composition, and not Inheritance

December 31st, 2010

A couple months ago, a Lisper who goes by Josh Marchán detailed the process he had been using to write programs in an Object Oriented style.

You first think of what kind of object you want to describe, and create a new class that has data fields that sort of describe that "object", then you write methods for that class. The hope here is that you can just inherit from whatever class, and everything will be okay.

I think this is a familiar process for many developers, including myself. However, Sykosomatic continued by pointing out the weaknesses that this style of programming presents, mainly that

In a similar vein, Raganwald pokes fun at inheritance and describes how OOP used backwards is POO.

The abstract ontology is derived from the behaviour of the individual specimens, the ontology does not define the behaviour of the individual specimens.

OOP gets this backwards. In OOP you define the ontology up front in the form of classes or prototypes. You then derive individual instances or objects from the ontology. The OOP ontology isn't a way of sketching what we observe, it's blueprint for building what we observe, nearly the exact opposite of the naturalist's ontology.

...

Changes and re-arrangements break existing code, because all of the specimens, the instances and objects, depend on the various classes, interfaces, and other constructs that define their behaviour. Is this a problem? Yes again, because computer programs change constantly. Discovery of new requirements, and new information is the norm, not the exception. OOP programs built as towering hierarchies of classes are like perfect crystals, to be admired by architects everywhere but loathed by the programmers responsible for maintaining them.

He goes on to champion an alternative way forward.

What if objects exist as encapsulations, and the communicate via messages? What if code re-use has nothing to do with inheritance, but uses composition, delegation, even old-fashioned helper objects or any technique the programmer deems fit? The ontology does not go away, but it is decoupled from the implementation.

Sykosomatic has a similar revelation after he discovers that the code base for his Chillax project becomes much cleaner when he codes in terms of generic functions and protocols rather than classes.

The beauty here is that you can reuse code without dealing with inheritance, or any sort of class-based hierarchy.

However, these ideas are nothing new. In fact, they are as old as the term "Object Oriented", which was coined by Alan Kay.

Just a gentle reminder that I took some pains at the last OOPSLA to try to remind everyone that Smalltalk is not only NOT its syntax or the class library, it is not even about classes. I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea.

The big idea is "messaging".

Does an object respond to a set of messages? Does it have X, Y, and Z methods? If so, we can use it; who cares if it inherits from a certain parent class or not?

function bad (foo) {
  if ( ! (foo instanceof Bar) ) {
    throw new TypeError("This is an annoying restriction!");
  }
  return foo.baz(foo.quux(10));
}

function good (foo) {
  if ( !foo.baz || !foo.quux ) {
    throw new TypeError("We need foo to have baz and quux methods.");
  }
  return foo.baz(foo.quux(10));
}

This paradigm is sometimes referred to as duck typing. Wikipedia describes duck typing as "a style of dynamic typing in which an object's current set of methods and properties determines the valid semantics, rather than its inheritance from a particular class."

The classic example is if it walks like a duck, swims like a duck, and quacks like a duck, it must be a duck. I prefer Cheech and Chong's explanation:

Looks like dog shit, smells like dog shit, feels like dog shit, tastes like dog shit. Must be dog shit. Good thing we didn't step in it!

So what am I getting at? I present to you a very simple method to support duck typing as a first class concept in JavaScript.

quacksLike

Just ask if a given object quacksLike you want it to. You can describe the messages you want an object to respond to with the key, and the type that you expect in the slot by using a constructor as the value.

var arrayish = {
  length: Number
};

var iterable = {
  filter: Function,
  forEach: Function,
  map: Function
};

(function () {

  console.log(quacksLike(arguments, arrayish));
  // true, because the arguments object has the value
  // 3 in its length slot.

  console.log(quacksLike(arguments, iterable));
  // false, because the arguments object is not blessed
  // with the `map`, `filter`, and `forEach` methods.

}(1, "a", {}));

The definition of the function quacksLike is given below.

var quacksLike = function (obj, definition) {
  var k, ctor;
  for ( k in definition ) {
    ctor = definition[k];
    if ( ctor === Number ) {
      if ( Object.prototype.toString.call(obj[k]) !== "[object Number]"
           || isNaN(obj[k]) ) {
        return false;
      }
    } else if ( ctor === String ) {
      if ( Object.prototype.toString.call(obj[k])
           !== "[object String]" ) {
        return false;
      }
    } else if ( ! (obj[k] instanceof ctor) ) {
      return false;
    }
  }
  return true;
};

Do we really need to formalize duck typing with quacksLike? Strictly, no. You could call the methods that you expect to exist and let the the errors be raised if the object was missing that method, or you could check by hand that they exist like I did in the above function good. I think the latter is better than the former because it makes your expectations and dependencies explicit. quacksLike is an easier-to-use formalization of this pattern.

Composition

Composing objects to create new ones is a powerful technique that allows us to easily re-use code for multiple objects. Surprisingly enough, even the Gang Of Four reccomended using composition in favor of inheritance in Design Patterns.

var foo = { 0: "a", 1: "b", 2: "c", length: 3 };
var fooPlusPlus = Object.combine(Array.prototype, foo);

console.log(quacksLike(foo, iterable));
// false
console.log(quacksLike(fooPlusPlus, iterable));
// true
fooPlusPlus.forEach(console.log.bind(console));
// a
// b
// c

I have defined Object.combine using ES5's Object.getOwnPropertyNames for convenience. Besides making the implementation clearer, it also allows us to compose objects which include the non-enumerable methods of an object, such as those on Array.prototype. If we were to use a for-in loop instead of Object.getOwnPropertyNames, we wouldn't iterate over and copy concat, forEach, etc. This is an ongoing trend in my code examples for this blog post. I would rather communicate ideas than deal with browser quirks, or incomplete ES5 implementations.

Object.combine = function () {
  var newObj = {},
      i      = 0,
      args   = Array.prototype.slice.call(arguments),
      len    = args.length;
  function copyProperty(k) {
    newObj[k] = args[i][k];
  }
  for ( ; i < len; i++ ) {
    Object.getOwnPropertyNames(args[i]).forEach(copyProperty);
  }
  return newObj;
};

An Example

Here is a less trivial example: a mini DOM manipulation library. To be trendy, the global entry point will be $. This library uses quacksLike to determine the DOM model and the correct way to attach event listeners, the module pattern in multiple places to encapsulate private data, and Object.combine to re-use code that has already been written for us by others in Array.prototype.

window.$ = (function () {

  var undef, listen, customAPI;


  // Private function to turn `thing` in to an array,
  // optionally slicing the first `n` elems off.

  function toArray(thing, n) {
    return Array.prototype.slice.call(thing, n || 0);
  }


  // Private function to partially apply the first n arguments
  // to `fn`.

  function curry (fn) {
    var args = toArray(arguments, 1);
    return function () {
      return fn.apply(this, args.concat(toArray(arguments)));
    };
  }


  // Private function to set the attribute `attr` of a single
  // DOM node `node` to `val`.

  function nodeAttributeSetter (attr, val, node) {
    node.setAttribute(attr, val);
  }


  // Private function to get the value of the attribute `attr`
  // on the DOM node `node`.

  function nodeAttributeGetter (attr, node) {
    return node.getAttribute(attr);
  }


  // Do the work to determine the correct method for attaching
  // event listeners only once by using `quacksLike` and an
  // immediately invoked function.

  listen = (function () {

    var w3c = {
          addEventListener: Function
        },
        ie = {
          attachEvent: Function
        };

    if ( quacksLike(document.body, w3c) ) {
      return function (event, fn, node) {
        node.addEventListener(event, fn, false);
      };
    } else if ( quacksLike(document.body, ie) ) {
      return function (event, fn, node) {
        node.attachEvent("on" + event, fn);
      };
    } else {
      throw new TypeError("Do not know how to attach event listeners.");
    }

  }());


  // This object is a mixin to be composed with the NodeList
  // results of querying the DOM by CSS selector. It provides
  // handy methods for getting and setting attributes,
  // attaching event listeners, and performing sub-queries by
  // CSS on these elements.

  customAPI = {

    attr: function (attr, val) {
      if ( val !== undef ) {
        this.forEach(curry(nodeAttributeSetter, attr, val));
        return this;
      } else {
        return this.length > 0
          ? nodeAttributeGetter(attr, this[0])
          : "";
      }
    },

    on: function (event, fn) {
      this.forEach(curry(listen, event, fn));
      return this;
    },

    find: function (selector) {
      var res = [];
      this.forEach(function (node) {
        res.push.apply(res, toArray(node.querySelectorAll(selector)));
      });
      return Object.combine(res, customAPI);
    }

  };

  return function (selector, context) {
    context = context || document;
    return Object.combine(Array.prototype,
                          context.querySelectorAll(selector),
                          customAPI);
  };

}());

The mini DOM library might be used like this:

// The following two forms are equivalent.
// They select the same elements.

$("div").find("p");
$("div p");


// Disable all links.

$("a").on("click", function (event) {
  event.preventDefault();
  alert("Don't leave me all alone!");
});


// Get the id of the first paragraph.

$("p").attr("id");


// Disable all input elementss.

$(input").attr("disabled", "disabled");


// Do stuff with each matched element.

$("div.foo").forEach(function (el) {
  doStuffWith(el);
});

Conclusion

To conclude, we should recognize that OOP does not need to be an all or nothing proposition. You can take the parts that are beneficial to you (such as message passing, encapsulation, and composition) and leave the parts which you may not require (such as inheritance).

Finally, take everything I say with a grain of salt (as you should with everything you read on the internet).

References

« Previous Entry

Next Entry »


Recent Entries

Naming `eval` Scripts with the `//# sourceURL` Directive on December 5th, 2014

wu.js 2.0 on August 7th, 2014

Come work with me on Firefox Developer Tools on July 8th, 2014

Debugging Web Performance with Firefox DevTools - Velocity 2014 on June 26th, 2014

Beyond Source Maps on March 12th, 2014

Memory Tooling in Firefox Developer Tools in 2014 on March 4th, 2014

Hiding Implementation Details with ECMAScript 6 WeakMaps on January 13th, 2014

Re-evaluate Individual Functions in Firefox Developer Tools' Scratchpad on November 22nd, 2013

Testing Source Maps on October 2nd, 2013

Destructuring Assignment in ECMAScript 6 on August 15th, 2013

Creative Commons License

Fork me on GitHub