JavaScript中的组合,inheritance和聚合

网上有很多关于组合与inheritance的信息,但是我还没有find像JavaScript这样的好例子。 使用下面的代码来演示inheritance:

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 */ }我只是把它们放在那里Stock.total = function(){ /* code */ } )。 如果组合在OOP中被很多情况所青睐,那么大多数使用JavaScript的人似乎只使用原型和inheritance? 我没有在网上find很多关于JavaScript构成的信息,只有其他语言。

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

在处理构图与inheritance时,语言是无关紧要的。 如果你明白什么是什么类,什么是一个类的实例 ,那么你有你所需要的。

构成就是当一个类其他类组成时, 或者换句话说,对象的一个​​实例具有对其他对象的实例的引用。

inheritance是一个类inheritance另一个类的方法和属性。

假设您有两个functionA和B.您想要定义第三个functionC,它具有A和B中的一些或全部。您可以使C扩展B和A,在这种情况下,C具有一切B因为C isA AB和A,或者你可以让C的每个实例都有一个A的实例和一个B的实例,并调用这些function上的项目。 在后一种情况下,每个实例C实际上包装B的实例和A的实例。

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

现在,对于具体的语言细节…

我用了class这个词,但是javascript没有Class的概念。 它有对象,那就是它(除了简单的types)。 Javascript使用原型inheritance,这意味着它有一个高效定义对象和方法在这些对象上的方法(这是另一个问题的主题,你可以search,因为已经有答案)。

所以,以上面的例子来说,你有A,B和C.

为了inheritance,你会有的

 // 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“isA”都是A.

Javascript没有多重inheritance*(后面会详细介绍),所以你不能让C扩展A和B.你可以使用组合来赋予它的function。 的确,这是构成被某些人inheritance的原因之一。 组合function没有限制(但这不是唯一的原因)。

 function C(){ this.a = new A(); this.b = new B(); } // someMethod on C invokes the someMethod on BCsomeMethod = function(){ this.a.someMethod() } 

所以有你的简单的例子inheritance和组成。 但是,这不是故事的结尾。 我之前说过,Javascript不支持多重inheritance,从某种意义上说它不支持多重inheritance,因为不能将多个对象原型的对象的原型build立在一个基础上。 即你不能这样做

 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的function,然后将两者的function都添加到了C ,所以C每个实例都有方法1,2和3.在这种特殊情况下, extend方法(由框架提供)所有这些都是为了build立物体原型的重要工作。

编辑 – 在您的评论中,您提出了一个很好的问题,即“如果使用构图,您如何将主要对象的范围与主要对象构成的对象的范围相协调”。

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

 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中,函数是对象,所以您可以将它们分配给variables。

这里的call调用是将someMethodOnA的作用域设置为this ,这是C的实例。

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

乍一看,提供的示例似乎不是用JavaScript演示组合的最佳select。 Stock构造函数的prototype属性对于访问任何股票对象的属性都仍然是两种方法的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); 

网上有很多关于组合与inheritance的信息,但是我还没有find像JavaScript这样的好例子。 …

我没有在网上find很多关于JavaScript构成的信息,只有其他语言。 …

也许search查询不够具体,但即使在2012年search“JavaScript混音组合”应该导致了一个不那么糟糕的方向。

…如果组合在OOP中被很多情况所青睐,那么大多数使用JavaScript的人似乎只使用原型和inheritance?

因为他们中的大多数人使用了他们所得到的和/或他们所熟悉的东西。 也许应该有更多关于JavaScript的知识作为基于授权的语言传播,并且可以用它来实现。

附录:

这是相关的线程,最近更新,并希望帮助…

  • stackoverflow.com: 如何在Javascript中正确使用mixins
  • 在javascript中的性状
  • stackoverflow.com :: Javascript区分组成与inheritance

我想我可以告诉你如何使用普通的JavaScript(ES5)以“对象组合”的方式重写你的代码。 我使用工厂函数而不是构造函数来创build对象实例,所以不需要new关键字。 这样,我可以优先对象增强(组合)而不是古典/伪古典/原型inheritance ,所以不会调用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); }; }; 

在这里拨弄。