JavaScript中的特征

21

我该如何在JavaScript中实现特征(trait)?

3个回答

23
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);
};

示例:

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]

将 TOrd.useBy 从 Rational 构造函数中移出,以便在原型上进行操作。 - Thomas Eding
真棒!古老的语言!崭新的思维。 - asyncwait
3
很遗憾没有逐行解释。 - Thielicious
FTR: 修复Rational.equalTo(q)函数 { return this.numerator * q.denominator === q.numerator * this.denominator; } - Jack Punt
FTR: 修复Rational.equalTo(q)函数 { return this.numerator * q.denominator === q.numerator * this.denominator; } - undefined

5

有不同的方法,同时也有生产就绪的库。

Mixins 是跨类层次结构重用代码的最古老形式。它们需要按线性顺序组合,因为 Mixins 的概念不包括/识别冲突解决功能。

Traits 是精细的代码重用单元,也适用于类级别;但它们更灵活,因为 Traits 必须提供组合、排除或别名方法的组合运算符。

我建议阅读两篇论文,它们都涵盖了一个与库无关的基于纯函数的 Mixins / Traits / Talents 方法。

  1. JavaScript Mixins 的新视角,作者 Angus Croll,发布于 2011 年 5 月
  2. JavaScript 的许多才能,用于概括面向角色编程方法,如 Traits 和 Mixins,发布于 2014 年 4 月。

纯函数和基于委托的混合机制与下面给出的两个示例一样简单明了...

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"

第一个示例在“实例”级别上执行,第二个示例涵盖“类”级别。

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

如果需要已建立和/或生产就绪的库,应该仔细查看。
  1. traits.js
  2. CocktailJS

再见

附录一

请参考:

附录二

由于我偶尔会涉及到这个问题,所以我想对它做出一些最终的思考...

无库通用方法(如上所述)只适用于非常细粒度的可组合行为重用单元。因此,只要不遇到超过1或2个容易解决的冲突,基于Angus Croll的Flight Mixins等模式就是要遵循的路径。

如果涉及到真正的特征,则必须有一个抽象层次。这个层次(例如提供一种类似DSL的语法糖)需要隐藏特征之间的组合复杂性或在应用特征时(当将特征行为应用于对象/类型时)进行冲突解决的复杂性。

从我的角度来看,现在在SO上有3个例子完全符合OP的要求...

如何在javascript中实现traits?


3

我强烈建议您查看trait.js库。他们还有一篇关于通用模式及其具体实现的相当不错的文章。我最近已经将它集成到我的项目中,效果非常好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接