JavaScript 中的组合、继承和聚合
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8696695/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Composition, Inheritance, and Aggregation in JavaScript
提问by Brian
There is a lot of information about composition vs inheritance online, but I haven't found decent examples with JavaScript. Using the below code to demonstrate inheritance:
网上有很多关于组合与继承的信息,但我还没有找到像 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(); // 5.19
(To make the code smaller, you can leave out the method implementations, like: Stock.total = function(){ /* code */ }
I just put them in there to be fancy). If composition is favored for a lot of situations in OOP, how come most people using JavaScript seem to only use prototypes and inheritance? I did not find a lot of information about composition in JavaScript online, only in other languages.
(为了使代码更小,您可以省略方法实现,例如:Stock.total = function(){ /* code */ }
我只是把它们放在那里是为了花哨)。如果 OOP 中的很多情况都喜欢组合,为什么大多数使用 JavaScript 的人似乎只使用原型和继承?我在网上没有找到很多关于 JavaScript 组合的信息,只有在其他语言中。
Can someone give me an example using the above code to demonstrate composition and aggregation?
有人可以给我一个使用上述代码的示例来演示组合和聚合吗?
回答by hvgotcodes
The language is irrelevant when dealing with composition vs inheritance. If you understand what class is and what an instanceof a class is, then you have all you need.
在处理组合与继承时,语言无关紧要。如果您了解什么是类以及类的实例是什么,那么您就拥有了所需的一切。
Composition is simply when a class is composedof other classes; or to say it another way, an instance of an object has references to instances of other objects.
组合只是当一个类由其他类组成时;或者换一种说法,一个对象的实例引用了其他对象的实例。
Inheritance is when a class inherits methods and properties from another class.
继承是指一个类从另一个类继承方法和属性。
Let's say you have two functionality, A and B. You want to define a third functionality, C, that has some or all of both A and B. You could either make C extend from B and A, in which case C has everything B and A has because C isA
B and A, or you can make each instance of C have an instance of A and an instance of B, and invoke items on those functionalities. In the latter case, each instance C in effect wraps an instance of B and an instance of A.
假设您有两个功能 A 和 B。您想定义第三个功能 C,它具有 A 和 B 的部分或全部。您可以使 C 从 B 和 A 扩展,在这种情况下,C 拥有 B 的所有内容和 A 有因为 C isA
B 和 A,或者你可以让 C 的每个实例都有一个 A 的实例和一个 B 的实例,并调用这些功能的项目。在后一种情况下,每个实例 C 实际上都包含一个 B 的实例和一个 A 的实例。
Of course, depending on the language, you might not be able to have a class extend from 2 classes (e.g. Java doesn't support multiple inheritance), but that's a language specific detail that has nothing to do with the concept.
当然,根据语言,您可能无法从 2 个类扩展一个类(例如,Java 不支持多重继承),但这是与该概念无关的语言特定细节。
Now, for the language specific details...
现在,对于语言特定的细节......
I used the word class, but javascript has no notion of Class as such. It has objects, and thats it (other than the simple types). Javascript uses prototypal inheritance, which means it has a way of efficiently defining objects and the methods on those objects (this is the topic for another question; you can search SO as there are already answers.)
我使用了class一词,但 javascript 没有 Class 的概念。它有对象,仅此而已(简单类型除外)。Javascript 使用原型继承,这意味着它有一种有效定义对象和这些对象上的方法的方法(这是另一个问题的主题;您可以搜索 SO,因为已经有答案。)
So going with our example above, you have A, B, and C.
所以继续我们上面的例子,你有 A、B 和 C。
For inheritance, you would have
对于继承,你会有
// define an object (which can be viewed as a "class")
function A(){}
// define some functionality
A.prototype.someMethod = function(){}
If you wanted C to extend A, you would do
如果你想让 C 扩展 A,你会这样做
C.prototype = new A();
C.prototype.constructor = A;
Now every instance of C would have the method someMethod
, because every instance of C "isA" A.
现在 C 的每个实例都有方法someMethod
,因为 C 的每个实例“都是 A”。
Javascript doesn't have multiple inheritance* (more on this later), so you can't have C extend both A and B. You can use composition, however, to give it the functionality. Indeed, this is one of the reasons composition is preferred by some over inheritance; there are no limits on combining functionality (but this isn't the only reason).
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()
}
So there are your simple examples for both inheritance and composition. However, this is not the end of the story. I said before that Javascript does not support multiple inheritance, and in a sense it doesn't, because you can't base the prototype of an object off the prototypes of multiple objects; i.e. you can't do
因此,这里有关于继承和组合的简单示例。然而,这并不是故事的结局。我之前说过Javascript不支持多重继承,从某种意义上说它不支持,因为你不能将一个对象的原型建立在多个对象的原型之上;即你不能做
C.prototype = new B();
C.prototype.constructor = B;
C.prototype.constructor = A;
because as soon as you do the third, line, you just undid the the second line. This has implications for the instanceof
operator.
因为一旦你做了第三行,你就取消了第二行。这对instanceof
运营商有影响。
However, this doesn't really matter, because just because you can't redefine the constructor of an object twice, you can still add any methods you want to the prototype of an object. So just because you can't do the above example, you can still add anything you want to C.prototype, including all the methods on the prototypes of both A and B.
然而,这并不重要,因为仅仅因为你不能两次重新定义一个对象的构造函数,你仍然可以在一个对象的原型中添加任何你想要的方法。所以仅仅因为你不能做上面的例子,你仍然可以在 C.prototype 中添加任何你想要的东西,包括 A 和 B 的原型上的所有方法。
Many frameworks support this and make it easy. I do a lot of Sproutcore work; with that framework you can do
许多框架都支持这一点并使其变得容易。我做了很多 Sproutcore 的工作;使用该框架,您可以做到
A = {
method1: function(){}
}
B = {
method2: function(){}
}
C = SC.Object.extend(A, B, {
method3: function(){}
}
Here I defined functionality in object literals A
and B
, and then added the functionality of both to C
, so every instance of C has methods 1, 2, and 3. In this particular case, the extend
method (provided by the framework) does all the heavy lifting of setting up the prototypes of the objects.
在这里,我在对象字面量A
and 中定义了功能B
,然后将两者的功能添加到 中C
,因此 C 的每个实例都有方法 1、2 和 3。在这种特殊情况下,该extend
方法(由框架提供)完成了所有繁重的工作设置对象的原型。
EDIT -- In your comments, you bring out a good question, namely "If you use composition, how do you reconcile the scope of the main object against the scope of the objects of which the main object is composed".
编辑 - 在您的评论中,您提出了一个很好的问题,即“如果您使用组合,您如何协调主对象的范围与组成主对象的对象的范围”。
There are a bunch of ways. The first is simply to pass arguments. So
有很多方法。第一个是简单地传递参数。所以
C.someMethod = function(){
this.a.someMethod(arg1, arg2...);
}
Here you are not messing with scopes, you are simply passing arguments around. This is a simple and very viable approach. (the arguments can come from this
or be passed in, whatever...)
在这里,您并没有弄乱作用域,您只是在传递参数。这是一种简单且非常可行的方法。(参数可以来自this
或传入,无论如何......)
Another way to do it would be to use the call
(or apply
) methods of javascript, which basically allows you to set the scope of a function.
另一种方法是使用javascript的call
(或apply
)方法,它基本上允许您设置函数的范围。
C.someMethod = function(){
this.a.someMethod.call(this, arg1, arg2...);
}
to be a bit more clear, the following is equivalent
更清楚一点,以下是等效的
C.someMethod = function(){
var someMethodOnA = this.a.someMethod;
someMethodOnA.call(this, arg1, arg2...);
}
In javascript, functions are object, so you can assign them to variables.
在 javascript 中,函数是对象,因此您可以将它们分配给变量。
the call
invocation here is setting the scope of someMethodOnA
to this
, which is the instance of C.
call
这里的调用是设置someMethodOnA
to的范围this
,它是 C 的实例。
回答by Peter Seliger
... Can someone give me an example using the above code to demonstrate composition and aggregation?
...有人可以给我一个使用上述代码的示例来演示组合和聚合吗?
At first glance the provided example does not seem to be the best
choice in order to demonstrate composition in JavaScript. The prototype
property of the Stock
constructor function still remains the ideal
place for both methods total
and list
for both do access any stock
object's own properties.
乍一看,提供的示例似乎不是演示 JavaScript 组合的最佳选择。构造函数的prototype
属性Stock
仍然是这两种方法的理想位置,total
并且list
它们都可以访问任何股票对象自己的属性。
What can be done is decoupling the implementations of these methods from the constructors prototype and providing them back exactly there - yet in an additional form of code reuse - Mixins ...
可以做的是将这些方法的实现从构造函数原型中解耦出来,并将它们准确地提供给那里——但以一种额外的代码重用形式——混合......
example:
例子:
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);
There is a lot of information about composition vs inheritance online, but I haven't found decent examples with JavaScript. ...
I did not find a lot of information about composition in JavaScript online, only in other languages. ...
网上有很多关于组合与继承的信息,但我还没有找到像 JavaScript 的例子。...
我在网上没有找到很多关于 JavaScript 组合的信息,只有在其他语言中。...
Maybe the search query was not specific enough but even in 2012 searching for "JavaScript Mixin composition" should have led into a not that bad direction.
也许搜索查询不够具体,但即使在 2012 年搜索“JavaScript Mixin composition”也应该会导致一个不错的方向。
... If composition is favored for a lot of situations in OOP, how come most people using JavaScript seem to only use prototypes and inheritance?
... 如果 OOP 中的很多情况都喜欢组合,为什么大多数使用 JavaScript 的人似乎只使用原型和继承?
Because most of them use, what they got tought and/or what they are familar with. Maybe there should be more knowledge spread about JavaScript as delegation based language too and what can be achieved with it.
因为他们中的大多数人都在使用,他们知道什么和/或他们熟悉什么。也许应该传播更多关于 JavaScript 作为基于委托的语言的知识,以及用它可以实现什么。
appendix:
附录:
This are related threads, recently updated and hopefully helping ...
这是相关的线程,最近更新,希望能帮助...
- stackoverflow.com :: How to use mixins properly in Javascript
- stackoverflow.com :: Traits in javascript
- stackoverflow.com :: Javascript Distinguish between Composition vs. Inheritance
- stackoverflow.com ::如何在 Javascript 中正确使用 mixins
- stackoverflow.com :: javascript 中的特性
- stackoverflow.com :: Javascript 区分组合与继承
回答by Glenn Mohammad
I think I can show you how to rewrite your code in "object composition" fashion by using plain JavaScript (ES5). I use factory functions instead of constructor functionsfor creating an object instance, so no new
keyword needed. That way, I can favour object augmentation (composition) over classical/pseudo-classical/prototypal inheritance, so no Object.create
function is called.
我想我可以向您展示如何使用纯 JavaScript (ES5) 以“对象组合”的方式重写您的代码。我使用工厂函数而不是构造函数来创建对象实例,因此不需要new
关键字。这样,我可以支持对象增强(组合)而不是经典/伪经典/原型继承,因此不会Object.create
调用任何函数。
The resulting object is a nice flat-composed object:
生成的对象是一个很好的平面组合对象:
/*
* 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(); // 5.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);
};
};
Fiddle here.
在这里摆弄。