假设我有以下“类树”:
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(通过合并原型(prototype))时,来自 Element 的构造函数将被调用两次,这可能是一个问题,具体取决于它的作用:
var Rectangle = function() {
Positionnable.call( this ); // Calls Element constructor
Sizeable.call( this ); // Calls Element constructor
};
所以我考虑了两种可能性:在某处添加 bool 值,当调用构造函数时将其设置为 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 中最好避免使用 multiple inheritance一起使用mixins必要时共享功能。多重继承会导致各种问题,包括臭名昭著的 dreaded diamond of doom问题(这是您面临的问题)。
不同的语言使用不同的方法来解决菱形问题。例如 Java 使用 interfaces C++ 使用 virtual inheritance 而不是多重继承解决歧义和语言,如 Python 和 Dylan 使用 method resolution order线性化 heterarchy .
尽管如此,上述所有技术都不足以解决您面临的钻石构造函数问题。接口(interface)在 JavaScript 等动态语言中毫无用处:鸭子类型扮演着更重要的 Angular 色。虚拟继承不必要地使事情复杂化。线性化解决了继承方法的顺序,但没有解决您的问题。
在 JavaScript 中可以使用两种技术来解决这个问题:
- 像大多数 JavaScript 程序员一样,完全放弃多重继承并使用 mixin 来共享功能。
- 根据
this
是否是派生类构造函数的实例,有条件地从派生类构造函数调用基类构造函数。
我建议您使用 mixins。但是,最终决定权在您。我将代表我简单介绍每种技术的优点和缺点。
混合
Mixins 是在 JavaScript 中实现多重继承的传统方式,大多数时候传统方法被证明是最好的。 mixin 就像一个 Java 接口(interface)带有实现。您可以(并且以我的拙见)重构您的代码如下:
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 中,最佳做法是创建继承自单个基类的类,并根据需要使用混合来共享其他功能。
好处:编程很像英语。名词就像类,动词就像方法,形容词就像混音。以“able” 结尾的词通常是形容词。因此,positionable
和 sizable
应该作为 mixins 实现。
类
如果您仍然一心想将 Positionable
和 Sizable
实现为类,那么恐怕我无法改变您的想法。因此,我将向您展示如何使用类解决您的问题,并说明为什么这种技术不如使用混合:
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);
如您所见,这段代码肯定比使用mixin 编写的代码更难看。在 Positionable
和 Sizable
构造函数中,我们在调用 Element
构造函数之前检查 this
是否是相应构造函数的实例, 解决菱形构造函数问题。
我们使用 Underscore.js extend
函数将 Positionable.prototype
和 Sizable.prototype
的属性复制到 Rectangle.prototype
上。我将留给您去弄清楚为什么这不是实现多重继承的推荐方法。
关于javascript - 多重继承和多次调用父构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18591702/