JavaScript中的组合,继承和聚合

Bri*_*ian 57 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
Run Code Online (Sandbox Code Playgroud)

(为了使代码更小,你可以省略方法实现,比如:Stock.total = function(){ /* code */ }我只是把它们放在那里是为了花哨).如果组合在OOP的很多情况下都受到青睐,为什么大多数使用JavaScript的人似乎只使用原型和继承呢?我没有在网上找到很多关于组合的信息,只有其他语言.

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

hvg*_*des 75

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

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

继承是指类从其他类继承方法和属性的时间.

假设您有两个功能,A和B.您想要定义第三个功能,C,它具有A和B中的部分或全部.您可以从B和A进行C扩展,在这种情况下C具有所有B并且A具有因为C isAB和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(){}
Run Code Online (Sandbox Code Playgroud)

如果你想让C扩展A,你会这样做

C.prototype = new A();
C.prototype.constructor = A;
Run Code Online (Sandbox Code Playgroud)

现在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()
}
Run Code Online (Sandbox Code Playgroud)

所以有继承和组合的简单示例.然而,这不是故事的结局.我之前说过,Javascript不支持多重继承,从某种意义上说它不支持,因为你不能将对象的原型基于多个对象的原型; 即你做不到

C.prototype = new B();
C.prototype.constructor = B;
C.prototype.constructor = A;
Run Code Online (Sandbox Code Playgroud)

因为只要你做第三行,你只需要解开第二行.这对instanceof运营商有影响.

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

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

A = {
   method1: function(){}
}

B = {
   method2: function(){}
}

C = SC.Object.extend(A, B, {
   method3: function(){}
}
Run Code Online (Sandbox Code Playgroud)

在这里,我在对象文字定义的功能AB,然后加入到两者的功能性C,所以C的每一个实例都具有方法1,2,和3.在此特定情况下,该extend方法(由框架提供)完成所有的繁重设置对象的原型.

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

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

C.someMethod = function(){
    this.a.someMethod(arg1, arg2...);
}
Run Code Online (Sandbox Code Playgroud)

在这里你没有搞乱范围,你只是传递参数.这是一种简单且非常可行的方法.(争论可以来自this或传入,无论......)

另一种方法是使用javascript 的call(或apply)方法,它基本上允许你设置一个函数的范围.

C.someMethod = function(){
    this.a.someMethod.call(this, arg1, arg2...);
}
Run Code Online (Sandbox Code Playgroud)

更清楚一点,以下是相同的

C.someMethod = function(){
    var someMethodOnA = this.a.someMethod;
    someMethodOnA.call(this, arg1, arg2...);
}
Run Code Online (Sandbox Code Playgroud)

在javascript中,函数是对象,因此您可以将它们分配给变量.

call调用这里的范围设定someMethodOnAthis,这是C的实例