【问题标题】:Multiple inheritance and parent constructor called mutiple times多次调用多重继承和父构造函数
【发布时间】:2013-09-06 15:41:28
【问题描述】:

假设我有以下“类树”:

              Element
               /   \
              /     \
             /       \
  Positionnable     Sizeable
             \        /
              \      /
               \    /
             Rectangle

现在说元素构造函数做了一些事情:

var Element = function() {
    this.traits = [ ];
};

并且说子类构造函数需要在执行自己的工作之前调用其父构造函数(元素构造函数):

var Positionnable = function() {
    Element.call( this );
    this.traits.position = { x : 0, y : 0 }; // Requires this.traits to be set.
};

var Sizable = function() {
    Element.call( this );
    this.traits.size = { w : 0, h : 0 }; // Requires this.traits to be set.
};

问题是,当我让 Rectangle 从 Positionnable 和 Sizable 继承时(通过合并原型),来自 Element 的构造函数将被调用两次,这可能是一个问题,具体取决于它所做的事情:

var Rectangle = function() {
    Positionnable.call( this ); // Calls Element constructor
    Sizeable.call( this );      // Calls Element constructor
};

所以我想到了两种可能性:在某个地方添加布尔值,当调用构造函数时将其设置为 true 以避免多次执行此操作,但这看起来很脏。

或者我可以调用 Rectangle 中的所有直接或间接父构造函数:

var Positionnable = function() {
    this.traits.position = { x : 0, y : 0 }; // Assumes parent constructor has been called
};

var Sizable = function() {
    this.traits.size = { w : 0, h : 0 }; // Assumes parent constructor has been called
};

var Rectangle = function() {
    Element.call( this );
    Positionnable.call( this ); // Does no longer call Element constructor
    Sizeable.call( this );      // Does no longer call Element constructor
};

但这会假设 Element 构造函数在 Positionnable 和 Sizable 构造函数之前被调用(这意味着这两个构造函数在单独调用时会失败),这也将涉及(对于编码器)递归查找所有要调用的直接或间接父类他们的构造函数(如果继承树比这更复杂,可能会很烦人),如果我需要为 Rectangle 创建一个子类,我会遇到和现在一样的问题。

那么我怎么能只调用一次构造函数呢?

【问题讨论】:

标签: javascript object inheritance constructor traits


【解决方案1】:

在我看来,第一种方法比第二种方法更好。

但是,多重继承通常不是一个好主意。

随着时间的推移,你的代码会变得复杂。

如果我们在 Java 中思考一下,它会更有意义

Rectangle extends Element implements Positionable, Sizable

我认为您应该重新考虑代码的类层次结构。

编辑:

如果你这样做,那么我会这样做。 它没有经过测试。您应该尝试使用它,并添加更多功能。

function extend(subClass, superClass) {
    var superclasses = subClass._superclasses || (subClass._superclasses = []);
    addSuperClasses(superclasses, superClass._superclasses);
    superclasses.push(superClass);
    subClass.prototype.initialize = initialize;
}

function addSuperClasses(superclasses, list) {
    if (list) {
        for (var i = 0, l = list.length, superclass; i < l; i++) {
            superclass = list[i];
            if (superclasses.indexOf(superclass) === -1) {
                superclasses.push(superclass);
            }
        }
    }
}

function initialize() {
    if (!this._initialized) {
        this._initialized = true;
        var superclasses = this.constructor._superclasses;
        if (superclasses) {
            for (var i = 0, l = superclasses.length; i < l; i++) {
                superclasses[i].call(this);
            }
        }
    }
}

function Element() {}

function Positionable() {
    this.initialize();
    this.traits.position = {
        x: 0,
        y: 0
    };
}
extend(Positionable, Element);

function Sizable() {
    this.initialize();
    this.traits.size = {
        w: 0,
        h: 0
    };
}
extend(Sizable, Element);

function Rectangle() {}
extend(Rectangle, Positionable);
extend(Rectangle, Sizable);

【讨论】:

  • 感谢您的回答。 Positionable 和 Sizable 实际上不仅仅是接口,我需要它们带来自己的属性。
  • 在你的方法中,在我看来,会有一些元素类可以定位,但不会变大,反之亦然。如果所有元素类都可以定位和调整大小,为什么要将它们分成两个单独的类?
  • " 会有一些元素类是可定位的,但不会变大,反之亦然"。这是正确的。例如,圆盘将具有半径,并且仅从 Positionnable 继承。 var Disc = function() { Positionnable.call( this ); this.traits.radius = 0; }; 这就是为什么我不能让,例如,Positionnable 扩展 Sizeable,反之亦然。
  • 谢谢,我也看看这个。
【解决方案2】:

传统上,在 JavaScript 中,最好完全避免使用 multiple inheritance,并在必要时使用 mixins 来共享功能。多重继承会导致各种问题,包括臭名昭著的dreaded diamond of doom 问题(这是您面临的问题)。

不同的语言使用不同的方法来解决钻石问题。例如,Java 使用 interfaces 而不是多重继承,C++ 使用 virtual inheritance 来解决歧义,而 Python 和 Dylan 等语言使用 method resolution order 来线性化 heterarchy

尽管如此,上述所有技术都不足以解决您面临的菱形构造问题。接口在 JavaScript 这样的动态语言中是无用的:鸭子类型扮演着更重要的角色。虚拟继承不必要地使事情复杂化。线性化解决了继承方法的顺序,但不能解决您的问题。

您可以采用两种技术来解决 JavaScript 中的这个问题:

  1. 完全放弃多重继承,并像大多数 JavaScript 程序员一样使用 mixin 来共享功能。
  2. 根据this 是否为派生类构造函数的实例,有条件地从派生类构造函数调用基类构造函数。

我建议您使用 mixins。然而,最终的决定是你自己做的。我将代表我简单地介绍每种技术的优点和缺点。

混合

Mixins 是在 JavaScript 中实现多重继承的传统方法,而且大多数时候传统方法被证明是最好的。 mixin 就像一个Java 接口带有 实现。您可以(并且应该以我的拙见)重构您的代码,如下所示:

function Element() {
    // constructor logic
}

function positionable(that, x, y) {
    that.x = x;
    that.y = y;
}

function sizable(that, w, h) {
    that.w = w;
    that.h = h;
}

function Rectangle(x, y, w, h) {
    Element.call(this);
    positionable(this, x, y);
    sizable(this, w, h);
}

Rectangle.prototype = Object.create(Element.prototype);

正如您在上面的代码中看到的那样,positionablesizable 不是构造函数。它们是 mixins - 你不应该使用它们来创建实例。您可以使用它们来扩充个实例。

在 JavaScript 中,最好的做法是创建从单个基类继承的类,并根据需要使用 mixin 来共享其他功能。

奖励:编程很像英语。名词就像类,动词就像方法,形容词就像混入。以 "able" 结尾的词通常是形容词。因此positionablesizable 应该被实现为mixins。

如果您仍然一心想将PositionableSizable 实现为类,那么恐怕我无法改变您的想法。因此,我将向您展示如何使用类来解决您的问题,并说明为什么这种技术不如使用 mixins:

function Element() {
    this.traits = [];
}

function Position() {
    if (this instanceof Position) Element.call(this);
    this.traits.position = { x: 0, y: 0 };
}

Position.prototype = Object.create(Element.prototype);


function Sizable() {
    if (this instanceof Sizable) Element.call(this);
    this.traits.size = { w: 0, h: 0 };
}

Sizable.prototype = Object.create(Element.prototype);

function Rectangle() {
    Element.call(this);
    Positionable.call(this);
    Sizable.call(this);
}

Rectangle.prototype = Object.create(Element.prototype);
_.extend(Rectangle.prototype, Positionable.prototype, Sizable.prototype);

如您所见,这段代码肯定比使用 mixins 编写的代码更丑陋。在PositionableSizable构造函数中,我们在调用Element构造函数之前检查this是否是各自构造函数的实例,解决了菱形构造函数问题。

我们使用 Underscore.js extend 函数将Positionable.prototypeSizable.prototype 的属性复制到Rectangle.prototype 上。我会让你弄清楚为什么这不是实现多重继承的推荐方法。

【讨论】:

  • 感谢您提供完整的答案,我会在下班后阅读:)
  • 好的,我看看能不能用 mixins 做一些元素。
猜你喜欢
  • 2011-11-16
  • 2018-10-10
  • 2012-04-24
  • 1970-01-01
  • 2011-07-06
  • 2015-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多