说我有以下“班级树”:
Element
/ \
/ \
/ \
Positionnable Sizeable
\ /
\ /
\ /
Rectangle
现在说元素构造函数做了一些事情:
var Element = function() {
this.traits = [ ];
};
并说子类构造函数需要在执行自己的工作之前调用它们的父构造函数(Element构造函数):
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.
};
问题是,当我从Positionnable和Sizable继承Rectangle(通过合并原型)时,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创建一个子类,我会遇到与现在相同的问题。
那么我怎样才能让构造函数只调用一次?
答案 0 :(得分:4)
传统上,在JavaScript中,最好完全避免使用multiple inheritance,并在必要时使用mixins来共享功能。多重继承会导致各种问题,包括臭名昭着的dreaded diamond of doom问题(这是您面临的问题)。
不同的语言使用不同的方法来解决钻石问题。例如,Java使用interfaces而不是多重继承,C ++使用virtual inheritance来解决歧义,而Python和Dylan等语言使用method resolution order来线性化heterarchy。
尽管如此,所有上述技术都不足以解决您所面临的钻石构造问题。接口在JavaScript等动态语言中毫无用处:鸭子打字起着更重要的作用。虚拟继承不必要地使问题复杂化。线性化解决了继承方法的顺序,但没有解决您的问题。
您可以使用两种方法在JavaScript中解决此问题:
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);
正如您在上面的代码中看到的那样positionable
和sizable
不是构造函数。它们是mixins - 你不应该使用它们来创建实例。您可以使用它们扩充实例。
在JavaScript中,最佳做法是创建从单个基类继承的类,并使用mixins根据需要共享其他功能。
奖金:编程很像英语。名词就像类,动词就像方法,形容词就像mixins。以“able”结尾的单词通常是形容词。因此,positionable
和sizable
应该以mixins的形式实现。
如果你仍然倾向于将Positionable
和Sizable
作为课程实施,那么恐怕我无法改变你的想法。因此,我将向您展示如何使用类解决您的问题,并演示为什么这种技术不如使用mixin:
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编写的代码更加丑陋。在Positionable
和Sizable
构造函数内部,我们在调用this
构造函数之前检查Element
是否是相应构造函数的实例,从而解决钻石构造函数问题。
我们使用Underscore.js extend
函数将Positionable.prototype
和Sizable.prototype
的属性复制到Rectangle.prototype
。我将留给你,弄清楚为什么这不是推荐的实现多重继承的方法。
答案 1 :(得分:0)
在我看来,第一种方法比第二种方法更好。
但是,拥有多重继承通常不是一个好主意。
随着时间的推移会使代码复杂化。
如果我们在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);