web-dev-qa-db-ja.com

JavaScriptの構成、継承、および集約

オンラインでの構成と継承に関する多くの情報がありますが、JavaScriptで適切な例は見つかりませんでした。以下のコードを使用して継承を実証します。

_function Stock( /* object with stock names and prices */ ) {
    for (var company_name in arguments[0]) {
        // copy the passed object into the new object created by the constructor
        this[company_name] = arguments[0][company_name]; 
    }
}

// example methods in prototype, their implementation is probably redundant for
// this question, but list() returns an array with toString() invoked; total()
// adds up the stock prices and returns them. Using ES5 feature to make
// inherited properties non-enumerable 

Stock.prototype =  {
    list: function () {
        var company_list = [];
        for (var company_name in this)
            company_list.Push(company_name);
        return company_list.toString();
    },
    total: function () {
        var price_total = 0;
        for (var company_name in this)
            price_total += this[company_name];
        return '$' + price_total;
    }
};

Object.defineProperties(Stock.prototype, {
    list: { enumerable: false },
    total: { enumerable:false }
});

var portfolio = new Stock({ MSFT: 25.96, YHOO: 16.13, AMZN: 173.10 });
portfolio.list();  // MSFT,YHOO,AMZN
portfolio.total(); // $215.19
_

(コードを小さくするために、次のようにメソッドの実装を省くことができます:Stock.total = function(){ /* code */ } OOPの多くの状況で合成が好まれている場合、JavaScriptを使用するほとんどの人がプロトタイプと継承のみを使用するように見えるのはなぜですか?他の言語でのみ、オンラインのJavaScriptで作曲に関する多くの情報を見つけませんでした。

誰かが上記のコードを使用して構成と集約を示す例を教えてもらえますか?

57
Brian

構成と継承を扱う場合、言語は無関係です。クラスが何であり、クラスのinstanceが何であるかを理解していれば、必要なものはすべて揃っています。

構成とは、クラスが他のクラスのcomposedである場合のみです。別の言い方をすれば、オブジェクトのインスタンスは他のオブジェクトのインスタンスへの参照を持っています。

継承とは、クラスが別のクラスからメソッドとプロパティを継承する場合です。

AとBの2つの機能があるとします。AとBの両方または一部を含む3番目の機能Cを定義するとします。CをBとAから拡張することもできます。 Aは、C isA BおよびAであるため、またはCの各インスタンスにAのインスタンスとBのインスタンスを持たせ、それらの機能の項目を呼び出すことができるためです。後者の場合、実際の各インスタンスCは、BのインスタンスとAのインスタンスをラップします。

もちろん、言語によっては、2つのクラスからクラスを拡張することはできないかもしれません(たとえばJavaは多重継承をサポートしません)が、それはコンセプトとは関係のない言語固有の詳細です。

次に、言語固有の詳細について...

Word classを使用しましたが、javascriptにはClassの概念はありません。オブジェクトがあり、それだけです(単純型以外)。 Javascriptはプロトタイプ継承を使用します。つまり、オブジェクトとそれらのオブジェクトのメソッドを効率的に定義する方法があります(これは別の質問のトピックです。すでに答えがあるのでSOを検索できます)。

上記の例では、A、B、Cがあります。

継承のために、あなたは

// define an object (which can be viewed as a "class")
function A(){}

// define some functionality
A.prototype.someMethod = function(){}

CにAを拡張させたい場合は、次のようにします。

C.prototype = new A();
C.prototype.constructor = A;

これで、CのすべてのインスタンスはメソッドsomeMethodを持つことになります。これは、CのすべてのインスタンスがAであるためです。

Javascriptには多重継承がありません*(これについては後で説明します)。したがって、CでAとBの両方を拡張することはできません。ただし、コンポジションを使用して機能を付与することができます。確かに、これは、継承よりも合成が好ましい理由の1つです。機能の組み合わせに制限はありません(ただし、これが唯一の理由ではありません)。

function C(){
   this.a = new A();
   this.b = new B();
}

// someMethod on C invokes the someMethod on B.
C.someMethod = function(){
    this.a.someMethod()
}

したがって、継承と構成の両方の簡単な例があります。しかし、これで話は終わりではありません。 Javascriptは多重継承をサポートしていませんが、ある意味ではサポートしていません。なぜなら、複数のオブジェクトのプロトタイプからオブジェクトのプロトタイプを作成することはできないからです。つまり、あなたはできない

C.prototype = new B();
C.prototype.constructor = B;
C.prototype.constructor = A;

3行目を実行するとすぐに、2行目を元に戻したからです。これは、instanceof演算子に影響を与えます。

ただし、オブジェクトのコンストラクタを2回再定義できないという理由だけでオブジェクトのプロトタイプに必要なメソッドを追加できますであるため、これは実際には重要ではありません。上記の例を実行できないからといって、C.prototypeに必要なものを追加できます(AとBの両方のプロトタイプのすべてのメソッドを含む)。

多くのフレームワークがこれをサポートし、簡単にします。私はSproutcoreの仕事をたくさんしています。そのフレームワークでできること

A = {
   method1: function(){}
}

B = {
   method2: function(){}
}

C = SC.Object.extend(A, B, {
   method3: function(){}
}

ここでは、オブジェクトリテラルAおよびBで機能を定義し、Cに両方の機能を追加したため、Cのすべてのインスタンスにはメソッド1、2、3があります。この特定のケースでは、extendメソッド(フレームワークが提供)がオブジェクトのプロトタイプのセットアップのすべての面倒な作業を行います。

編集-コメントで、「構成を使用する場合、メインオブジェクトの範囲を、メインオブジェクトを構成するオブジェクトの範囲に対してどのように調整しますか」という良い質問を出します。

たくさんの方法があります。 1つ目は、単に引数を渡すことです。そう

C.someMethod = function(){
    this.a.someMethod(arg1, arg2...);
}

ここでは、スコープをいじるのではなく、単に引数を渡すだけです。これはシンプルで実行可能なアプローチです。 (引数はthisから取得することも、渡すこともできます...)

別の方法は、javascriptのcall(またはapply)メソッドを使用することです。これにより、基本的に関数のスコープを設定できます。

C.someMethod = function(){
    this.a.someMethod.call(this, arg1, arg2...);
}

もう少し明確にするために、以下は同等です

C.someMethod = function(){
    var someMethodOnA = this.a.someMethod;
    someMethodOnA.call(this, arg1, arg2...);
}

JavaScriptでは、関数はオブジェクトなので、変数に割り当てることができます。

ここでのcall呼び出しは、someMethodOnAのスコープをCのインスタンスであるthisに設定しています。

75
hvgotcodes

...構成と集約を示すために、上記のコードを使用した例を教えてもらえますか?

一見、提供された例は、JavaScriptで構成を示すための最良の選択ではないようです。 prototypeコンストラクター関数のStockプロパティは、両方のメソッドtotallistが両方のストックオブジェクト自身のプロパティにアクセスするための理想的な場所です。

できることは、これらのメソッドの実装をコンストラクターのプロトタイプから切り離し、そこに正確に戻すことです-さらにコードの再利用の形で-Mixins ...

例:

var Iterable_listAllKeys = (function () {

    var
        Mixin,

        object_keys = Object.keys,

        listAllKeys = function () {
            return object_keys(this).join(", ");
        }
    ;

    Mixin = function () {
        this.list = listAllKeys;
    };

    return Mixin;

}());


var Iterable_computeTotal = (function (global) {

  var
      Mixin,

      currencyFlag,

      object_keys = global.Object.keys,
      parse_float = global.parseFloat,

      aggregateNumberValue = function (collector, key) {
          collector.value = (
              collector.value
              + parse_float(collector.target[key], 10)
          );
          return collector;
      },
      computeTotal = function () {
          return [

              currencyFlag,
              object_keys(this)
                  .reduce(aggregateNumberValue, {value: 0, target: this})
                  .value
                  .toFixed(2)

          ].join(" ");
      }
    ;

    Mixin = function (config) {
        currencyFlag = (config && config.currencyFlag) || "";

        this.total = computeTotal;
    };

    return Mixin;

}(this));


var Stock = (function () {

  var
      Stock,

      object_keys = Object.keys,

      createKeyValueForTarget = function (collector, key) {
          collector.target[key] = collector.config[key];
          return collector;
      },
      createStock = function (config) { // Factory
          return (new Stock(config));
      },
      isStock = function (type) {
          return (type instanceof Stock);
      }
  ;

  Stock = function (config) { // Constructor
      var stock = this;
      object_keys(config).reduce(createKeyValueForTarget, {

          config: config,
          target: stock
      });
      return stock;
  };

  /**
   *  composition:
   *  - apply both mixins to the constructor's prototype
   *  - by delegating them explicitly via [call].
   */
  Iterable_listAllKeys.call(Stock.prototype);
  Iterable_computeTotal.call(Stock.prototype, {currencyFlag: "$"});

  /**
   *  [[Stock]] factory module
   */
  return {
      isStock : isStock,
      create  : createStock
  };

}());


var stock = Stock.create({MSFT: 25.96, YHOO: 16.13, AMZN: 173.10});

/**
 *  both methods are available due to JavaScript's
 *  - prototypal delegation automatism that covers inheritance.
 */
console.log(stock.list());
console.log(stock.total());

console.log(stock);
console.dir(stock);

オンラインでの構成と継承に関する多くの情報がありますが、JavaScriptで適切な例は見つかりませんでした。 ...

他の言語でのみ、オンラインのJavaScriptで作曲に関する多くの情報を見つけることができませんでした。 ...

検索クエリは十分に具体的ではなかったかもしれませんが、2012年に「JavaScript Mixinの構成」を検索しても、それほど悪くない方向に導かれるはずでした。

... OOPの多くの状況で合成が好まれる場合、JavaScriptを使用するほとんどの人がプロトタイプと継承のみを使用するように見えるのはなぜですか?

彼らのほとんどが使用するものであるため、彼らが求めたもの、および/または身近なもの。たぶん、委任ベースの言語としてのJavaScriptと、それによって何が達成できるのかについて、より多くの知識が広がるはずです。

付録:

これは関連するスレッドであり、最近更新され、うまくいけば...

2
Peter Seliger

プレーンなJavaScript(ES5)を使用して、「オブジェクト構成」方式でコードを書き換える方法を紹介できると思います。オブジェクトインスタンスの作成にはコンストラクター関数ではなくファクトリー関数を使用しているため、newキーワードは不要です。そうすれば、古典的/擬似古典的/プロトタイプの継承よりもオブジェクトの拡張(構成))を優先できますなので、Object.create関数が呼び出されます。

結果のオブジェクトは、ニースのフラットに構成されたオブジェクトです。

/*
 * Factory function for creating "abstract stock" object. 
 */
var AbstractStock = function (options) {

  /**
   * Private properties :)
   * @see http://javascript.crockford.com/private.html
   */
  var companyList = [],
      priceTotal = 0;

  for (var companyName in options) {

    if (options.hasOwnProperty(companyName)) {
      companyList.Push(companyName);
      priceTotal = priceTotal + options[companyName];
    }
  }

  return {
    /**
     * Privileged methods; methods that use private properties by using closure. ;)
     * @see http://javascript.crockford.com/private.html
     */
    getCompanyList: function () {
      return companyList;
    },
    getPriceTotal: function () {
      return priceTotal;
    },
    /*
     * Abstract methods
     */
    list: function () {
      throw new Error('list() method not implemented.');
    },
    total: function () {
      throw new Error('total() method not implemented.');
    }
  };
};

/*
 * Factory function for creating "stock" object.
 * Here, since the stock object is composed from abstract stock
 * object, you can make use of properties/methods exposed by the 
 * abstract stock object.
 */
var Stock = compose(AbstractStock, function (options) {

  return {
    /*
     * More concrete methods
     */
    list: function () {
      console.log(this.getCompanyList().toString());
    },
    total: function () {
      console.log('$' + this.getPriceTotal());
    }
  };
});

// Create an instance of stock object. No `new`! (!)
var portofolio = Stock({MSFT: 25.96, YHOO: 16.13, AMZN: 173.10});
portofolio.list(); // MSFT,YHOO,AMZN
portofolio.total(); // $215.19

/*
 * No deep level of prototypal (or whatsoever) inheritance hierarchy;
 * just a flat object inherited directly from the `Object` prototype.
 * "What could be more object-oriented than that?" –Douglas Crockford
 */ 
console.log(portofolio); 



/*
 * Here is the magic potion:
 * Create a composed factory function for creating a composed object.
 * Factory that creates more abstract object should come first. 
 */
function compose(factory0, factoryN) {
  var factories = arguments;

  /*
   * Note that the `options` passed earlier to the composed factory
   * will be passed to each factory when creating object.
   */
  return function (options) {

    // Collect objects after creating them from each factory.
    var objects = [].map.call(factories, function(factory) {
      return factory(options);
    });

    // ...and then, compose the objects.
    return Object.assign.apply(this, objects);
  };
};

フィドル ここ

2
Glenn Mohammad