Search code examples
javascriptinheritancebackbone.jsmixinscomposition

Mixin pattern in Backbone - how does it differ from Backbone's extend implementation?


Recently I have been reading up on JavaScript patterns and architecture in general. One tip I often encountered was to favor composition over inheritance, so I spent the day digging a bit deeper into this.

Having understood the advantages of a composition (more like mix'n'match, i.e. via factory) compared to inheritance (kind of inflexible and vague coupling), I wondered how to make practical use of this knowledge, which lead me to the Mixin pattern.

Since I mainly develop in Backbone, I created a simple mixin to use in a view. Actually, I just moved parts of a baseview into a mixin:

Before:

var BaseView = Backbone.View.extend({
  getTemplate: function () {
    return template;
  }
});

var MyView = BaseView.extend({
  template: 'dummy',      

  initialize: function () {
    // Do stuff
  },

  render: function () {
    // Do render stuff
  }
});

After:

var myMixin = {
  getTemplate: function () {
    return template;
  }
};

var MyView = Backbone.View.extend({
  template: 'dummy'

  initialize: function () {
    _.extend(this, myMixin)
    // Do stuff
  },

  render: function () {
    // Do render stuff
  }
});

Now the getTemplate method is injected to MyView using Underscore's extend function, but how is this different from inheriting from BaseView, which also makes use of a different implementation of extend calling BaseView.extend? Is this even real inheritance in Backbone?


Solution

  • Edit

    I'm sure you have come across in your career instances in which the word "merge" was not well-defined. "Mixin" has a precise meaning in other languages and concepts, but in Javascript, it carries the same level of precision and well-definition that "merge" does, which is to say, not much.

    You are correct to point out that in both instances you get the result of _.extend. That is the consequence of your particular choice of mixin implementation... which was to call _.extend in initialize.

    There are other choices of mixin that have more exotic and useful logic for handling conflicts in merged keys and so on. backbone.cocktail is one I remember.

    If you haven't looked at extend from the annotated source, please do below.

    Extend from the annotated source

    var extend = function(protoProps, staticProps) { var parent = this; var child;

    We start off with the function extend's signature...

    The constructor function for the new subclass is either defined by you (the “constructor” property in your extend definition), or defaulted by us to simply call the parent constructor.

    if (protoProps && _.has(protoProps, 'constructor')) {
      child = protoProps.constructor;
    } else {
      child = function(){ return parent.apply(this, arguments); };
    }
    

    In other words, either we already have a constructor for the child or we don't, in which case it gets the parents.

    Add static properties to the constructor function, if supplied.

    _.extend(child, parent, staticProps);
    

    Set the prototype chain to inherit from parent, without calling parent‘s constructor function and add the prototype properties.

    child.prototype = _.create(parent.prototype, protoProps);
    child.prototype.constructor = child;
    

    This is subtle but very important. Note that they could have just done: child.prototype = parent.prototype. Think about why they did not, and what that does for you.

    These two lines are the heart of the whole method by the way, because it 1) gives the child its own copy of the parents' prototype, and gives the prototype the child's constructor. So you get a prototype that is truly unique to the child but extended from a parent.

    Set a convenience property in case the parent’s prototype is needed later.

        child.__super__ = parent.prototype;
    
        return child;
      };
    

    What about underscore extend?

    Underscore extend is a method for merging. It is part of the process of Backbone.extend but the other part pertains to achieving inheritance.

    Now the mixin library cocktail for Backbone gives you a more intelligent merge handling than does underscore, so you may want to pursue further options if you feel like extending is too much.

    https://github.com/onsi/cocktail

    Orig

    Well, I am not claiming to fully materialize the diff between, say, extending from a hash and mixin a hash, but I do know a few things that are important.

    First, extend takes a second argument :) A lot of people don't know that.

    var extend = function(protoProps, staticProps) {
    

    Sometimes I have seen people use mixin because they want to consistently change what could be achieved thru staticProps.

    Second, extend semantically relates an extension to a Clazz... 99 times out of 100, you don't see the input to extend reused.