Javascript Inheritance Part 4: Putting it All Together

This is part 4 of a four part series on Javascript inheritance.
Part 1: Overview and Use Case
Part 2: Delegation
Part 3: Concatenation
Part 4: Putting it All Together

For this part of the series I will no refer to class-based inheritance as 'class inheritance' and prototypal inheritance as 'object composition'.

Final Terminology Clarification

There are several programming paradigms or ways of thinking about how to break down a problem so that your program can solve. Javascript is multi-paradigm, but one of the ways you can choose to structure your program is object-oriented, whose goal is to break a problem down into objects that interact with each other. In general, there are two styles of approaching how to determine the relationship between these objects: class inheritance and object composition. Another way to call these is class-based inheritance and prototype-based inheritance respectively. Or to use adjectives, classical inheritance and prototypal inheritance. Yes, this is a source of confusion. Many choose to refrain from using inheritance to refer to object composition.



Class inheritance and object composition, which are still at the paradigm level, both have several design patterns with which to implement object-orientated programming. I won't enumerate class inheritance implementations because Javascript has no support for a strict class inheritance, however composition can be achieved via delegation or concatenation. Delegation is when you follow essentially a linked list to resolve method names, concatenation is when you when all methods are attached to the base object directly.

In my implementations of a delegation pattern and a concatenation pattern, I did not include object creation because they are not technically part of either pattern. They are instead typically handled via what are known as factory functions in Javascript implementations. Factory functions are functions that return objects, they produce an object. Within them as a tool, you can build up,. You can see more about them here on youtube video by MPJ. They're essentially synonymous to constructors, but factory functions in Javascript tailor towards a composition style of making objects, whereas constructors tailor. In fact, in the a book about design patterns, the Gang of Four state that there is factory pattern.

If factory functions are the complement to constructors in object composition, prototypes and mixins are the complement to classes. A prototype are the blueprints of prototypal inheritance. I do not believe there is a specific categorisation for the way the delegation pattern uses them. Mixins are a type of prototype that groups functions to be used to mixed in with an object. This is the pattern that concatenation in Javascript.

Class-based Inheritance vs Prototypal Inheritance

The heart of the difference between the class inheritance and object composition is not in their implementations, but in their how they analyse problems. The former is usually described as an 'is-a' relationship between objects whereas the latter is a 'has-a' relationship, however both can be used to conceptualise any idea.

Class inheritance encourages you to build up a hierarchy of labels, a taxonomy, at the beginning of a project, a point where it is impossible to understand the relationship between objects. Because such a hierarchy is rigid in structure, unforeseen problems often require you to completely change the structure, and changes to your hierarchy's design are far-reaching. Object composition precludes object definition and only defines sets of behaviours making it more flexible, thus more future-proof. Further discussion of any advantages and disadvantages are beyond the scope of this post.

Class inheritance suffers from the diamond problem when a class inherits from multiple parent classes, and those parent classes themselves have a common parent whose methods they override differently. The class keyword only supports single inheritance so the pseudo-class pattern does not suffer from this problem. Both object composition patterns are also one-dimensional and allow the user to, at object creation, specify which method names are preferred over others.

I have not tested it myself, however several sources state that declaring objects with new as one does in class-based inheritance is the fastest way to create objects. Judging from my own implementations, I would say the concatenation pattern is indeed slower, but that the inflexible delegation pattern should be about as fast as the new keyword. However object creation is already such an inexpensive function performance-wise that in nearly all situations, it's not worth factoring it as a reason for choosing one pattern over another. The same goes for the difference in memory consumption between all the implementations.

Compiled Further Reading with Notes

"Favor 'object composition' over 'class inheritance'." (Gang of Four 1995:20)
From a book titled Design Patterns: Elements of Reusable Object-Oriented Software.

The most helpful resource for understanding was reading this blog post titled "A fresh look at Javascript Mixins" by Angus Croll. Croll's implementation of mixin blueprints, or 'functional mixins', uses the this keyword to circumvent using an extra function. Explores some ideas to expand upon the concept of mixin blueprints further.

Douglas Crockford, most famous for his book titled Javascript: The Good Parts, talks about it on several occasions. This talk given on October 17, 2006 is the most clear one I've found.

A youtuber named MPJ has made a video about factory functions and object composition over class inheritance that target this specific issue most clearly. Additionally MPJ's series on object creation in Javascript is quite relevant.

Aadit M. Shah goes fairly into depth on the topic in this and this stack exchange answer and in this blog post.

A stackexchange question looking at how Scala tries to do object composition, some food for thought. Also Eric Elliot giving a talk (linked on this blog post) titled "Classical Inheritance is Obsolete: How to Think in Prototypal OO" at the Fluent Conference in 2013. I think the added value over the other links is the QA section towards the end and comments the viability of composition over inheritance. Though very tagential to the subject, if you care about performance, this talk by John-David Dalton titled 'Unorthodox Performance' at ThunderPlains in 2015, particular how certain native methods are slow as well as implementation details.

A Final Implementation


// Decleration of NameOfMixin
(function (namespace) { // Closure for private variables
  let mixinName = 'NameOfMixin';   // So you don't have to rename several things and the mixin name appears at the top
  let _privateMap = new WeakMap(); // Perhaps with a native check since you can't polyfill WeakMaps with the same memory properties
  
  let that = {
    publicVar: '',
    method1: function (param) {
      let _private = _privateMap.get(this);
    },
    method2: function (param) {
      that.method1();
    },
  };
  
  namespace[mixinName] = that;
}(this));

// Decleration of NameOfAnotherMixin
(function (namespace) {
  let mixinName = 'NameOfAnotherMixin';
  let _privateMap = new WeakMap();
  
  let that = {
    anotherPublicVar: '',
    anotherMethod: function () {
      let _private = _privateMap.get(this);
    }
  };
  
  namespace[mixinName] = that;
}(this));

// Declaration of concatenation helper
function inheritConcatenate(spec) {
  let args = Array.prototype.slice.call(arguments);
  let i = 0;
  let len = args.length;
  let mixin, method;
  
  while ((++i) < len) {
    mixin = args[i];
    for (method in mixin) {
      if (mixin.hasOwnProperty(method)) {
        spec[method] = mixin[method];
      }
    }
  }
  return spec;
}

// Generator function
function cat(n) {
  let obj = { // Defaults
    sound: 'meow',
    size: '50px',
    name: n,
  };
  return inheritConcatenate(obj, NameOfMixin, NameOfAnotherMixin);
}

// Object creation
let ako = cat('Sebastian');
let bko = cat('Nemu');


Part 3: Concatenation

No comments:

Post a Comment

Note: only a member of this blog may post a comment.