1

I've read through a lot of JavaScript mixin design patterns, but didn't found one that suits my needs. Most of what I've seen uses some extent function that copies methods of one object to the prototype of another. For value properties this works just like with a method, but it fails for properties which are objects itself, because just copying them means every instance of my class will point to the same object for this property.

I made a code example with a simple point class:

var Point = function ( x, y ) {
    this.x = x;
    this.y = y;
};

Point.prototype = {
    constructor: Point,
    print: function () {
        console.log( this.x + this.offset[0], this.y + this.offset[1] );
    }
};

The print function uses an offset property which I haven't declared yet, because it originates from a Transformable class I want to mixin into my Point class:

var Transformable = {
    offset: [ 0, 0 ],
    setOffset: function ( x, y ) {
        this.offset[ 0 ] = x;
        this.offset[ 1 ] = y;
    }
};

To make the mixin I use some extent function like this:

var extent = function ( target, mixin ) {
    for ( var property in mixin ) {
        if ( !target.prototype[ property ] ) {
            target.prototype[ property ] = mixin[ property ];
        }
    }
};
extent( Point, Transformable );

Now the code compiles and can be used:

var myPoint1 = new Point( 1, 2 );
myPoint1.setOffset( 5, 5 );
var myPoint2 = new Point( 1, 2 );
myPoint1.print();
myPoint2.print();

But it prints "6 7" both of the times, because the offset array used in myPoint2 is the same as in myPoint1.

So how can I achieve that every point gets its own offset array while still originating from the mixin? It should work for every object besides arrays as well, because when not for the simplicity of a short example I would use a vector class here instead.

Actually this answer accomplishes what I want, but as a further requirement I want to be able to still use the object literal syntax for adding the methods of the Point class:

Point.prototype = {
    constructor: Point,
    print: function () {...},
    ...
};

I am however flexible as to how the syntax for the mixin object might look like.

Community
  • 1
  • 1
daign
  • 899
  • 1
  • 10
  • 18
  • Mixins that need instance initialisation also need to be mixed in the constructor. – Bergi Feb 04 '16 at 17:19
  • You might want to have a look [here](http://stackoverflow.com/a/21711249/1048572) – Bergi Feb 04 '16 at 17:24
  • @Bergi Thank you, maybe I should have looked for decorators instead of mixins from the start. Mixing inside the constructor is what I've considered myself in the meantime. – daign Feb 04 '16 at 17:47
  • Btw, it's `extend` not `extent` :-) – Bergi Feb 04 '16 at 17:49

3 Answers3

1

I have a working solution, but I myself would consider it controversial. Basically every time I'm mixing I use a new instance of my mixin class to copy from. So I can't actually extent my Point class, but rather do the mixin inside the constructor. (Using same extent function as in the question.)

var Transformable = function () {
    this.offset = [ 0, 0 ];
    this.setOffset = function ( x, y ) {
        this.offset[ 0 ] = x;
        this.offset[ 1 ] = y;
    };
};

var Point = function ( x, y ) {
    this.x = x;
    this.y = y;
    extent( this, new Transformable() );
};

Point.prototype = {
    constructor: Point,
    print: function () {
        console.log( this.x + this.offset[0], this.y + this.offset[1] );
    }
};

var myPoint1 = new Point( 1, 2 );
myPoint1.setOffset( 5, 5 );
var myPoint2 = new Point( 1, 2 );

myPoint1.print();
myPoint2.print();

Compared with the multiple inheritance answer I linked to in my question I think it's more readable, since I can stay with the object literal syntax for the Point class and I only have to state once from where to mixin.

However performance might be an issue since I'm not mixin into the class but into every single instance, although I don't know how grave that is.

I would welcome any precise argument why this might be a bad solution.

daign
  • 899
  • 1
  • 10
  • 18
  • If your mixin is class-like (a constructor), I'd rather use `Transformable.call(this)` instead of `extend(this, new Transformable)`. But when setting it up to be used as a mixin and not as a constructor of itself, make the function so that it can be called as `transformable(this);`. – Bergi Feb 04 '16 at 17:50
  • You still should have and mix methods into the prototype where applicable. Like a normal class, your mixing should do both. – Bergi Feb 04 '16 at 17:53
1

Answering your 2nd post; No, there is nothing bad with this solution except from what Bergi already did mention - a function based Mixin should never be instantiated but always be applied to an object by either call or apply. Performance won't be an issue. JS is lightning fast with handling objects, function delegation and dealing with closures. Your win is separation of concerns, code reuse, still sticking to ES3 language core (no need for 3rd party library), being able of introducing additional state whilst having control over how to expose or hide such additional state.

The refactored example provided with the original post:

var Transformable = function () {       // a function based mixin approach
    var offset = [0, 0];                // is library agnostic and already
                                        // at ES3 language core level enables
    this.setOffset = function (x, y) {  // composition as well as creation of
        offset[0] = x;                  // and/or passsing around additional
        offset[1] = y;                  // state.
    };
    this.getOffsetX = function () {
        return offset[0];
    };
    this.getOffsetY = function () {
        return offset[1];
    };
};

var Point = function (x, y) {

    this.x = x;
    this.y = y;
                                // applying the `Transformable` mixin
    Transformable.call(this);   // onto the new `Point` instance.
};
Point.prototype = {
    constructor: Point,
    print: function () {
        console.log(this.x + this.getOffsetX(), this.y + this.getOffsetY());
    }
};


var myPoint1 = new Point( 1, 2 );
myPoint1.setOffset( 5, 5 );

var myPoint2 = new Point( 1, 2 );

myPoint1.print(); // 6 7
myPoint2.print(); // 1 2

Still taking the OP's example as starting point, the next approach might be cleaner for a Points prototypal print method does not make any assumptions about methods that are neither part of the constructor nor of the prototype ...

var Transformable = function () {
    var offset = [0, 0];

    this.setOffset = function (x, y) {
        offset[0] = x;
        offset[1] = y;
    };
    this.getOffsetX = function () {
        return offset[0];
    };
    this.getOffsetY = function () {
        return offset[1];
    };

    return this;
};


var Point = function (x, y) {

    this.x = x;
    this.y = y;

    return this;
};
Point.prototype.print = function () {

    console.log(this.x, this.y);
};


var TransformablePoint = function () {

    return Transformable.call(Point.apply(this, arguments));
};
TransformablePoint.prototype.print = function () {

    console.log(this.x + this.getOffsetX(), this.y + this.getOffsetY());
};


var myPoint1 = new TransformablePoint( 1, 2 );
myPoint1.setOffset( 5, 5 );

var myPoint2 = new TransformablePoint( 1, 2 );

myPoint1.print(); // 6 7
myPoint2.print(); // 1 2
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • Your second approach resembles the standard Javascript inheritance design pattern very much. Something I've rarely used because for every function you have to start with `Namespace.Class.prototype.functionName =`. And most of the code would happen in TransformablePoint I guess. – daign Feb 05 '16 at 17:09
  • Concerning the first approach I don't think the usage of the mixed in methods and properties in `Point` is bad, since it's stated in the constructor that some functionality was inserted into this class. This is far better than most mixin design patterns found around the web, where the mixing only happens after both objects have been laid out, accessing all the mixins functionality beforehand without any remorse. – daign Feb 05 '16 at 17:17
  • It only looks that way due to the originally provided example code. `TransformablePoint` does not inherit anything from `Point`. The whole prototype chain is ignored. Instead it straightforward uses function based mixin composition, leveling the `Point` constructor as much to a mixin as `Transformable` is intended to be used. The only inheritance that takes place is for `print` (twice), with `Point` not even being in need of it. If I was knowing what your architecture is supposed to look like, I could choose the right pattern(s) for modeling it in a *dry* way. – Peter Seliger Feb 05 '16 at 17:30
  • Answering the second comment of yours, Jakob. You are most probably right. And the only thing that then in this case needs to be changed is the naming - from `Point` directly to `TransformablePoint `. – Peter Seliger Feb 05 '16 at 17:38
0

In ECMAScript6, I would use symbols to store internal data, and define the public properties as accessors which use that data:

var Transformable = (function() {
  var defaultOffset = [0, 0],
      offset = Symbol('offset');
  return {
    get offset() {
      if(!this[offset]) this[offset] = Object.create(defaultOffset);
      return this[offset];
    },
    setOffset: function ( x, y ) {
      this.offset[0] = x;
      this.offset[1] = y;
    }
  };
})();

In order to copy the descriptors, you will need a more sophisticated extend:

var extent = function(target, mixin) {
  Object.getOwnPropertyNames(mixin).forEach(function(prop) {
    var desc = Object.getOwnPropertyDescriptor(mixin, prop);
    Object.defineProperty(target, prop, desc);
  });
};
extent(Point.prototype, Transformable);

A that point you can also consider getting rid of setOffset and define a setter for offset so that you can use myPoint1.offset = [5, 5] instead.

Oriol
  • 274,082
  • 63
  • 437
  • 513
  • This works just like I need it. Although I will need more time to understand it, since I'm not used to ECMAScript6 yet. And I'm hesitating to adopt this, because I'm still using Chromium 37 on some configurations, since it's the still supported version in Ubuntu Precise Pangolin, which lacks support for Symbol. – daign Feb 04 '16 at 18:19