2

If I use constructor functions for my objects and prototype for shared functionality I would like to mixin shared functionality (functions) to the object's prototype but instance specific (this varaibles) to the object instances.

To add the prototype part I found this pattern. To set instance variables that are assumed to be there by the prototype functions I came up with an init (one for each mixin).

Here is a simple example:

var mixIn=function(target,source){
  for(fn in source){
    if(source.hasOwnProperty(fn)){
      target.prototype[fn]=source[fn];
    }
  }
};
var SpeakEnable = {
  say:function(){
    console.log(this.message);
  },
  initSpeak:function(){// for initializing instance vars
    this.message="Hello World Mixed in!";
    this.object=[];
  }
};
var Person=function(){
  this.initSpeak();//have to init instance vars
};
// set up inheritance
// set up Person.prototype
// set speak enable
mixIn(Person,SpeakEnable);

var lulu=new Person();
lulu.say();

var june=new Person();
console.log(june.say===lulu.say);//true
console.log(june.object===lulu.object);//false

This all works fine and dandy but initializing the instance variables is where I have some problem with. It somehow doesn't seem to be a very clean way. When I mix in several mixins the Person constructor function has to call all the init functions to set up the instance variables. Forgetting to call it will result in strange errors (in this case console logging undefined when say is called on an instance).

So the question is: is there a cleaner way to setup initial instance variables that are assumed to be there by the mixin functions?

HMR
  • 37,593
  • 24
  • 91
  • 160

1 Answers1

1

You could inherit all mixable objects from a base object that ensures proper initialization. This is a clean way of achieving your goal.

The following code demonstrates this principle:

//------------ framework

var inherits = function(childCtor, parentCtor) {
  function tempCtor() {};
  tempCtor.prototype = parentCtor.prototype;
  childCtor.superClass_ = parentCtor.prototype;
  childCtor.prototype = new tempCtor();
  childCtor.prototype.constructor = childCtor;
};

var mixIn=function(target,source){
  for(fn in source){
    if(source.hasOwnProperty(fn) && fn.name != 'init'){
      target.prototype[fn]=source[fn];
    }
  }

  if (typeof source.init == 'function') { 
      if (target.prototype._mixInits === undefined) { 
          target.prototype._mixInits = [];
      }
      target.prototype._mixInits.push(source.init);
  }
};

// all objects that can be mixin's should inherit from
// this object in order to ensure proper initialization
var Mixable = function() {
    var mixInits = this.__proto__._mixInits;
    if (mixInits !== undefined) {
        for (var i = 0; i < mixInits.length; i++) {
            mixInits[i].call(this);
        }
    }
};

//------------ testcode

var SpeakEnable = {
  say:function(){
    console.log(this.message);
  },
  init:function(){
    console.log('say init called');
    this.message="Saying Hello World Mixed in!";
    this.object=[];
  }
};

var WalkEnable =  {
  walk:function(){
    console.log(this.walk_message);
  },
  init:function(){
    console.log('walk init called');
    this.walk_message="Walking step 1.2.3.";
  }
};


var Person=function() {
  Mixable.call(this);
};

inherits(Person, Mixable);

mixIn(Person,SpeakEnable);
mixIn(Person,WalkEnable);

var lulu=new Person();
lulu.say();
lulu.walk();
Lodewijk Bogaards
  • 19,777
  • 3
  • 28
  • 52
  • Thank you, you can remove the inherrit since `Mixable.call(this)` is enough to initialise the instance. – HMR Jul 24 '13 at 00:23
  • True. However, I would consider this proper design though. You could also use it as a check to see if a mixIn is possible (i.e. in the mixIn function you would check if the target inherits from Mixable). It's true that this still doesn't guarantee the super's constructor is called, but it is more than nothing. – Lodewijk Bogaards Jul 24 '13 at 06:34