JavaScriptで特性を実装するにはどうすればよいですか?
3 に答える
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]
さまざまなアプローチがあり、その間に本番環境に対応したライブラリもあります。
ミックスインは、クラス階層全体でのコード再利用の最も古い形式です。Mixinsの概念は競合解決機能をカバー/認識しないため、これらは線形順序で構成する必要があります。
トレイトは、クラスレベルでも機能する、コードの再利用のきめ細かい単位です。ただし、トレイトはメソッドの組み合わせ、除外、またはエイリアシングのための合成演算子を提供する必要があるため、より柔軟性があります。
Mixins / Traits/Talentsのライブラリにとらわれない純粋関数ベースのアプローチをカバーしている2つの論文を読むことをお勧めします。
- 2011年5月のAngusCrollによるJavaScriptMixinsの最新情報
- 2014年4月からの特性やミックスインのような役割指向プログラミングアプローチを一般化するためのJavaScriptの多くの才能。
純粋関数と委任ベースのミックスインの仕組みは、次の2つの例で提供されるのと同じくらい簡単です...
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"
...最初の例は「インスタンス」レベルで動作し、2番目の例は「クラス」レベルをカバーしています...
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
確立されたライブラリや本番環境に対応したライブラリが必要な場合は、詳しく調べる必要があります。
さよなら
付録I
こちらもご覧ください:
- stackoverflow.com::Javascriptでミックスインを適切に使用する方法
- stackoverflow.com::Javascript特性パターンリソース
付録II
時々私は明らかにこの問題をいじっているので、私はそれにいくつかの最終的な考えを追加したくありません...
(上記のように)グルーコードが多すぎないライブラリにとらわれないアプローチは、動作の再利用の非常にきめの細かい構成可能なユニットに対してのみ機能します。したがって、1つまたは2つを超える簡単に解決可能な競合に遭遇しない限り、たとえば、AngusCrollのFlightMixinに基づくパターンがたどるパスです。
実際の特性に関しては、抽象化レベルが必要です。このレイヤー(たとえば、DSLのようなある種のシンタックスシュガーとして提供される)は、トレイトからトレイトを構成する、またはトレイト適用時(トレイトの動作がオブジェクト/タイプに適用されるとき)での競合解決などの複雑さを隠す必要があります。
これまでに、SOには3つの例があり、私の観点からは、OPが求めていたものを正確に提供しています…</ p>
JavaScriptで特性を実装するにはどうすればよいですか?
- stackoverflow.com::JSのコンポジションとミックスイン
- stackoverflow.com::BAbelでトランスパイルされたES6クラスのミックスイン
- stackoverflow.com::レガシーミックスインベースのクラス階層のリファクタリング
- stackoverflow.com::クラスを使用した多重継承
trait.jsライブラリをチェックアウトすることを強くお勧めします。また、一般的なパターンと具体的な実装についても非常に優れた記事があります。私は最近プロジェクトに組み込みましたが、それは魅力のように機能します。