JavaScript中的组合,继承和聚合

时间:2012-01-02 01:12:30

标签: javascript oop inheritance composition

关于组合与继承在线有很多信息,但我没有找到适合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的人似乎只使用原型和继承?我没有在网上找到很多关于组合的信息,只有其他语言。

有人可以使用上面的代码举例说明组合和聚合吗?

3 个答案:

答案 0 :(得分:75)

在处理组合与继承时,语言无关紧要。如果您了解哪个类是什么以及类的实例是什么,那么您就拥有了所需的一切。

组合就是一个类组成的其他类;或者换句话说,对象的实例引用了其他对象的实例。

继承是指一个类从另一个类继承方法和属性的时候。

假设您有两个功能,A和B.您想要定义第三个功能,C,它具有A和B中的部分或全部。您可以从B和A进行C扩展,在这种情况下C拥有B和A的所有东西,因为C isA B和A,或者你可以使C的每个实例都有A的实例和B的实例,并调用这些功能的项目。在后一种情况下,每个实例C实际上包装了B的实例和A的实例。

当然,根据语言的不同,您可能无法从2个类扩展类(例如,Java不支持多重继承),但这是与该概念无关的语言特定细节。

现在,针对特定语言的详细信息...

我使用了 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.但是,您可以使用合成来为其提供功能。实际上,这是一些原因,其中一些原因是某些优先于继承;组合功能没有限制(但这不是唯一的原因)。

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;

因为只要你做了第三行,你只需要解开第二行。这对instanceof运算符有影响。

然而,这并不重要,因为您无法重新定义对象的构造函数两次,您仍然可以将任何方法添加到对象的原型。所以只是因为你不能做上面的例子,你仍然可以添加任何你想要的C.prototype ,包括A和B原型的所有方法。

许多框架都支持这一点并使其变得简单。我做了很多Sproutcore的工作;您可以使用该框架

A = {
   method1: function(){}
}

B = {
   method2: function(){}
}

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

这里我在对象文字AB中定义了功能,然后将两者的功能添加到C,因此C的每个实例都有方法1,2和3。在这种特殊情况下,extend方法(由框架提供)完成了设置对象原型的所有繁重工作。

编辑 - 在你的评论中,你提出了一个很好的问题,即“如果你使用合成,你如何协调主要对象的范围与主要对象组成的对象的范围”。< / p>

有很多方法。第一个是简单地传递参数。所以

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的范围设置为this,这是C的实例。

答案 1 :(得分:2)

  

...   有人可以使用上面的代码给我一个例子来演示   组成和聚合?

乍一看,提供的示例似乎并不是最好的 选择以便在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 :(得分:2)

我想我可以告诉你如何在&#34;对象组合中重写你的代码&#34;使用纯JavaScript(ES5)时尚。我使用 工厂函数而不是构造函数 来创建对象实例,因此不需要/* * 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); }; }; 个关键字。这样,我可以支持 对象扩充(组合)而非经典/伪经典/原型继承 ,因此不会调用{{1}}函数。

生成的对象是一个很好的平面组合对象:

{{1}}

小提琴here