Multiple inheritance and a parent constructor called mutiple times

Let's say I have the following "class tree":

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

Now let's say that the element constructor does something:

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

And they say that subclass constructors require that their parent constructor (Element constructor) be called before doing their own work:

 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. }; 

The problem is that when the Rectangle is inherited from Positionnable and Sizable (by merging prototypes), the constructor from Element will be called twice, which can be a problem depending on what it does:

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

So, I thought of two possibilities: adding booleans somewhere where it would be set true when the constructor was called to avoid it more than once, but it looks dirty.

Or I could call ALL direct or indirect parent constructors in a 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 }; 

But this suggests that the Element constructor is called before the Positionnable and Sizable constructs (which means that the two will fail when calling separatly), which will also require (for the encoder) a recursive search for all direct or indirect parent classes to call their constructors (maybe , this is annoying if the inheritance tree is more complex than that), and I would have the same problem as I have now if I need to subclass Rectangle.

So, how could I create constructors only once?

+4
source share
2 answers

Traditionally, in JavaScript, it is better to avoid multiple inheritance and use mixins when necessary to share functions. Multiple inheritance leads to all kinds of problems, including the notorious problem, the terrible diamond threat (the problem you are facing).

Different languages ​​use different methods to solve the diamond problem. For example, Java uses interfaces instead of multiple inheritance, C ++ uses virtual inheritance to eliminate ambiguities, and languages ​​such as Python and Dylan use the method resolution order to linearize heterarchy .

However, all of the above methods are not effective enough to solve the diamond constructor problem that you are facing. Interfaces are useless in dynamic languages ​​such as JavaScript: duck printing plays a more significant role. Virtual inheritance unnecessarily complicates the situation. Linearization resolves the order of inherited methods, but does not solve your problem.

You can use two methods in JavaScript:

  • Abandon multiple inheritance in general and use mixins to share functions, as most JavaScript programmers do.
  • Conditionally call the constructor of the base class from the constructor of the derived class, depending on whether this instance of the constructor of the derived class.

I would recommend using mixins. However, the final decision is yours. On my behalf, I’ll just talk about the advantages and disadvantages of each technique.

Impurities

Mixins is a traditional way to implement multiple inheritance in JavaScript, and in most cases, traditional methods have proven to be the best. Smeshin is similar to Java s interface implementation. You could (and should, in my humble opinion) reorganize your code as follows:

 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); 

As you can see in the above code, positionable and sizable are not constructors. These are mixins - you should not use them to create instances. You use them to expand instances.

In JavaScript, it is best to create classes that inherit from the same base class, and use mixins to share additional features as needed.

Bonus: Programming is very similar to English. Nouns are like classes, verbs are like methods, and adjectives are like mixins. Words ending in "capable" are usually adjectives. Therefore, positionable and sizable must be implemented as mixins.

Classes

If you still intend to implement positionable and sizable classes as classes, I am afraid that I cannot do this to change my mind. Therefore, I will show you how to solve your problem with classes and demonstrate why this method is inferior to mixins use methods:

 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); 

As you can see, this code is definitely uglier than code written using mixins. Inside the positionable and sizable we check if this instance of the corresponding constructors before calling the Element constructor, which solves the problem of the diamond constructor.

We use the Underscore.js extend function to copy the Positionable.prototype and Sizable.prototype to Rectangle.prototype . I will leave this to you to understand why this is not the recommended method for implementing multiple inheritance.

+4
source

In my opinion, the first is better than the second approach.

However, it is generally not recommended to have multiple inheritance.

This will complicate your code over time.

If we think in Java for a second, it makes sense

 Rectangle extends Element implements Positionable, Sizable 

I think you should review the class hierarchy of your code.

EDIT:

If you do this, then here, how would I go. It is not verified. You should try and play with it and add additional features.

 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); 
0
source

Source: https://habr.com/ru/post/1500287/


All Articles