Traits in javascript [closed]

2019-01-06 12:57发布

问题:

How can I implement traits in javascript ?

回答1:

function Trait (methods) {
  this.traits = [methods];
};

Trait.prototype = {
    constructor: Trait

  , uses: function (trait) {
      this.traits = this.traits.concat (trait.traits);
      return this;
    }

  , useBy: function (obj) {
      for (var i = 0; i < this.traits.length; ++i) {
        var methods = this.traits [i];
        for (var prop in methods) {
          if (methods.hasOwnProperty (prop)) {
            obj [prop] = obj [prop] || methods [prop];
          }
        }
      }
    }
};

Trait.unimplemented = function (obj, traitName) {
  if (obj === undefined || traitName === undefined) {
    throw new Error ("Unimplemented trait property.");
  }
  throw new Error (traitName + " is not implemented for " + obj);
};

Example:

var TEq = new Trait ({
    equalTo: function (x) {
      Trait.unimplemented (this, "equalTo");
    }

  , notEqualTo: function (x) {
      return !this.equalTo (x);
    }
});

var TOrd = new Trait ({
    lessThan: function (x) {
      Trait.unimplemented (this, "lessThan");
    }

  , greaterThan: function (x) {
      return !this.lessThanOrEqualTo (x);
    }

  , lessThanOrEqualTo: function (x) {
      return this.lessThan (x) || this.equalTo (x);
    }

  , greaterThanOrEqualTo: function (x) {
      return !this.lessThan (x);
    }
}).uses (TEq);


function Rational (numerator, denominator) {
  if (denominator < 0) {
    numerator *= -1;
    denominator *= -1;
  }
  this.numerator = numerator;
  this.denominator = denominator;
}

Rational.prototype = {
    constructor: Rational

  , equalTo: function (q) {
      return this.numerator * q.numerator === this.denominator * q.denominator;
    }

  , lessThan: function (q) {
      return this.numerator * q.denominator < q.numerator * this.denominator;
    }
};

TOrd.useBy (Rational.prototype);

var x = new Rational (1, 5);
var y = new Rational (1, 2);

[x.notEqualTo (y), x.lessThan (y)]; // [true, true]


回答2:

I seriously recommend you to checkout the trait.js library. They also have quite a good article about the general pattern as well as their concrete implementation. I have recently built in into my project and it works like a charm.



回答3:

There are different approaches and in the meantime production ready libraries as well.

Mixins are the oldest form of code reuse across class hierarchies. They need to be composed in linear order since the concept of Mixins does not cover/recognize conflict resolution functionality.

Traits are fine grained units of code reuse that work on class level too; but they are more flexible since Traits have to provide composition operators for combination, exclusion or aliasing of methods.

I do recommend reading 2 papers both are covering a library agnostic pure function based approach for Mixins / Traits / Talents.

  1. A fresh look at JavaScript Mixins by Angus Croll from May 2011
  2. The many talents of JavaScript for generalizing Role Oriented Programming approaches like Traits and Mixins from April 2014.

The pure function and delegation based mixin mechanics is as straightforward as provided with the next 2 given examples ...

var Enumerable_first = function () {
  this.first = function () {
    return this[0];
  };
};
var list = ["foo", "bar", "baz"];

console.log("(typeof list.first)", (typeof list.first)); // "undefined"

Enumerable_first.call(list); // explicit delegation

console.log("list.first()", list.first()); // "foo"

... with the first example acting at "instance" level and the second one covering "class" level ...

var Enumerable_first_last = function () {
  this.first = function () {
    return this[0];
  };
  this.last = function () {
    return this[this.length - 1];
  };
};
console.log("(typeof list.first)", (typeof list.first));  // "function"   // as expected
console.log("(typeof list.last)", (typeof list.last));    // "undefined"  // of course

Enumerable_first_last.call(Array.prototype);  // applying behavior to [Array.prototype]

console.log("list.last()", list.last());      // "baz"  // due to delegation automatism

If one is in need for established and/or production ready libraries one should have a closer look on

  1. traits.js
  2. CocktailJS

so long

Appendix I

please see also:

  • stackoverflow.com :: How to use mixins properly in Javascript
  • stackoverflow.com :: Javascript Traits Pattern Resources

Appendix II

Since from time to time I'm apparently fiddle with this matter I wan't to add some final thoughts to it ...

The library agnostic approach without too much glue code (as mentioned above) does work only for very fine grained composable units of behavioral reuse. Thus, as long as one does not run into more than 1 or 2 easily resolvable conflicts, patterns based on e.g. Angus Croll's Flight Mixins are the path to follow.

If it comes to real traits, there has to be an abstraction level to it. This layer (e.g. provided as some sort of syntactic sugar like a DSL) needs to hide the complexity e.g. of composing traits from traits or of conflict resolution at a traits apply time (when a trait's behavior gets applied to an object/type).

By now there are 3 examples at SO that from my perspective provide exactly what the OP did ask for …

How can I implement traits in javascript ?

  • stackoverflow.com :: Compostions and mixins in JS
  • stackoverflow.com :: Mixins for ES6 classes, transpiled with babel
  • stackoverflow.com :: Refactoring legacy mixin-based class hierarchies
  • stackoverflow.com :: Multiple inheritance using classes