2

Why this question is not a duplicate

This answer javascript inheritance from multiple objects does not solve my problem (although it has been marked as a duplicate of my previous question), because it is not scalable as it violates the DRY principle.

For this to work, one would have to manually reference each method like this :

Foo2.prototype.a = function() { /*code...*/};
Foo2.prototype.b = function() { /*code...*/};
Foo2.prototype.c = function() { /*code...*/};
Foo2.prototype.d = function() { /*code...*/};
//and so on and so on...

And what if I have dozens of classes containing dozens of methods ? Should I really manually copy-paste the same reference for each and every class over and over again in my source code ? While this solution would work for a very low number of classes, it is just not usable in a large-scale application using dozens or hundreds of classes.

The problem I'm trying to solve

enter image description here

I'm trying to instantiate objects which must inherit all the properties and methods of Animal and Flying_object using new keyword.

var objA = new Fish(),
    objB = new Bird(),
    objC = new UFO();

Tricky part is Animal and Flying_object can NOT have a parent-child relationship.

I know JavaScript doesn't implement native methods for multi-inheritance, so I'm posting this question to get some help finding a custom, scalable solution to this problem.

Code example and expected behavior

var Living_being = function() { this.className = 'Living_being'; };

var Animal = function() {
    this.className = 'Animal';
    this.vector = {x: 0, y: 0};
}
Animal.prototype = new Living_being();
Animal.prototype.getClassName = function() { console.log('Instance of... '+ this.className); };
Animal.prototype.get_vector = function() { console.log(this.vector); }

var Flying_object = function() {
    this.className = 'Flying_object';
    this.value = 'some value';
}
Flying_object.prototype.getClassName = function() { console.log('Instance of... '+ this.className); };
Flying_object.prototype.get_val = function() { console.log(this.value); }

// So far so good...
var UFO = function() {};
UFO.protoype = new Flying_object(); //classical inheritance
var Fish = function() {};
Fish.protoype = new Animal(); //classical inheritance
// Now the tricky part: how to make Bird share all of the methods and properties of Animal and Flying_object ?
var Bird = function() {};
Bird.prototype = new ....(); //pseudocode where .... is a class containing all the properties of Animal and Flying_object

var instance = new Bird();

//expected result:
instance.getClassName();//--> Instance of...
instance.get_vector();  //--> {x: 0, y: 0}
instance.get_val();     //--> 'some value'

This is where I'm stuck. How can I make Bird inherit from BOTH Animal and Flying_object ? Any insight would be greatly apprenciated.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Hal_9100
  • 773
  • 1
  • 7
  • 17
  • Are you sure multiple inheritance is the best approach? Given your example, what it means for a bird to fly would seem to be quite different from that of a UFO. Sometimes trying to squeeze all your modeling into one concept like inheritance isn't the best way to go. –  Nov 20 '17 at 19:14
  • 4
    The efficient solution for multiple inheritance in JS is not to use it and to instead use composition or a number of other options. – Mörre Nov 20 '17 at 19:14
  • @rock star: In this simplified example we could indeed argue about this, but Flying_object really contains the exact same functions multiple classes in my app must use in order to work. I created Flying_object because I didn't want to copy-paste these functions in each and every class, but then I am stuck with this multi-inheritance problem – Hal_9100 Nov 20 '17 at 19:23
  • 1
    As I said, use composition. Create a combined prototype object with the methods of all that you want, you are not even limited to picking from prototype objects. Create a constructor that initializes both "parent" objects and assign as its prototype that composite prototype object. – Mörre Nov 20 '17 at 19:24
  • As @Mörre said, there are many options. Composition and interfaces are two very useful constructs. Just out of curiosity, what are you modeling in your code? –  Nov 20 '17 at 19:27
  • @Mörre object composition sounds like the way to go, but I can't find any article/tutorial not using ES6 syntax (which I can't use). If it doesn't take too much time, could you please post an example using the code provided in my question ? – Hal_9100 Nov 20 '17 at 19:53
  • @Hal_9100 As I wrote above, create a constructor function for the combo-type, which calls the respective constructor functions that you want to inherit from. Create a new object and copy the function references from the prototype objects of all prototype objects you want to inherit from (those assigned to the `prototype` properties of the constructor functions) into it. Assign it to the `prototype` property of the new combo constructor function. The functions you "copied" were only copied by reference, so there is no waste. – Mörre Nov 20 '17 at 19:59
  • @rock star: I'm trying to model shooting objects in an HTML5 game. Shooting objects come with really the same data structure as well as the same methods for instantiating, shooting, animating and recycling the bullets. Shooting objects are some enemies (not all of them), some collectable items, and the player itself, and so on. In the end, being able to shoot (i.e. inherit the data structure and methods allowing to do it) is something various game objects have in common, unrelated to their inheritance tree, so I guess this advocates in favor of object composition as Mörre and you suggest – Hal_9100 Nov 20 '17 at 19:59
  • I think you'll probably be happier with it in the long run. While you could say an object *is* a shooter among other things, it'll likely be simpler to think of this as an ability that an object possesses, with the shooter ability being its own entity. Best of luck! –  Nov 20 '17 at 20:10
  • @Mörre: I posted a code sample along with a working jsFiddle as an answer. Can you please tell me if this is what you were talking about ? I'm not sure my understanding is correct on this one... – Hal_9100 Nov 20 '17 at 20:14
  • @Hal_9100 As I just wrote above, your combo-"class"-constructor function needs to call all the constructor functions that you ""inherit" from. I mean, kind of obvious, right? You want them all to to their construction work on the new object. I highly recommend you check out all - ALL - videos from Douglas Crockford. Check Youtube. – Mörre Nov 20 '17 at 20:22

3 Answers3

1

Here is a working solution I came up with at some point and gave up because I though there might be a better solution.

@Mörre: I'm not sure this is what you advised me to do in the comments: is this what you called object composition ? Or am I going all wrong here ?

Demo: https://jsfiddle.net/Lau1989/4ba8yrc8/1/

function mix(classA, classB) {
    var instanceA = new classA(),
        instanceB = new classB();
    for (var prop in instanceA) {
        instanceB[prop] = instanceA[prop];
    }
    return instanceB;
}

var Bird = function() { this.className = 'Bird'; };
Bird.prototype = mix(Animal, Flying_object);
var instance = new Bird();
Hal_9100
  • 773
  • 1
  • 7
  • 17
  • What's wrong with this solution? Creating a new prototype with the methods of two other prototypes seems ideal. I realize mutations to one of the component prototypes doesn't cause a mutation in the mixed prototype, but otherwise, why did you find this unsatisfactory? – apsillers Nov 20 '17 at 20:18
  • As an aside, I'd recommend using `Object.create(classA)` instead of `new classA()` -- constructors could create instance-specific properties that don't belong on a prototype. – apsillers Nov 20 '17 at 20:21
0

If you need inherit from few classes you can extend prototype Bird from Animal and Ufo using jquery function $.extend({});. Example $.extend(Bird.prototype,Animal.prototype,UFO.prototype) or you can create custom extend function.If names properties or functions has same names , they will be rewrite.

I got this from documentation: Using Object.assign() extend only enumerable properties.The Object.assign() method only copies enumerable and own properties from a source object to a target object. It uses [[Get]] on the source and [[Set]] on the target, so it will invoke getters and setters. Therefore it assigns properties versus just copying or defining new properties. This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters. For copying property definitions, including their enumerability, into prototypes Object.getOwnPropertyDescriptor() and Object.defineProperty() should be used instead.

Petrashka Siarhei
  • 733
  • 10
  • 12
  • 1
    Pointing to some library function is not an explanation. Tell him what he has to do in the language. Do **you** yourself actually understand what that function does that you point him to? If so, tell that, if not, it's not a good answer if the person giving advice does not actually understand what's going on. – Mörre Nov 20 '17 at 19:16
  • 1
    Loading jQuery just to use `$.extend` seems like bad advice. Especially when `Object.assign` exists. –  Nov 20 '17 at 19:17
  • Object.assign() extend only enumerable properties. – Petrashka Siarhei Nov 20 '17 at 19:18
  • @СергейПетрашко: True, I forgot about that. The point still stands. jQuery is way overkill for this, and frankly for most modern code. –  Nov 20 '17 at 19:20
  • This limitation of `Object.assign` should not matter in this particular context. Who has non-enumerable functions on the prototype object...? Okay okay...I'm sure there are a couple of people, anything that is possible will actually be implemented by some guy. – Mörre Nov 20 '17 at 19:22
  • @Mörre: With classes, it's more and more common. Generally there's no reason for methods to be enumerable. –  Nov 20 '17 at 19:28
  • You can use getter or setter. This properties cann't copies. – Petrashka Siarhei Nov 20 '17 at 19:29
  • @rockstar You just saw a reason. Invert your statement... ---- getter/setter? Now we've almost reached peak complexity for a tiny problem. I'm sure we can still find a place for proxies.... – Mörre Nov 20 '17 at 19:29
  • @Mörre: I said *generally* there's no reason. Since you seem to also think that multiple inheritance isn't a good idea, I would think that you'd agree. –  Nov 20 '17 at 19:31
  • I agree with you , but sometimes need extend object prototypes, and it using – Petrashka Siarhei Nov 20 '17 at 19:34
  • @СергейПетрашко, ваш английский так же плохо, как мой русский :-) Ну, не важно. – Mörre Nov 20 '17 at 19:50
  • @Mörre: Все же не соглашусь, Ваш русский все же не так плох, как мой английский))) – Petrashka Siarhei Nov 20 '17 at 20:08
  • For non-enumerable properties, you can use `Object.getOwnPropertyNames`, and iterate the returned Array of property names. –  Nov 20 '17 at 20:11
0

JavaScript does not support the concept of multiple inheritance. It also does not implement syntax for mixin and/or trait based composition. But unlike the former, the latter can be achieved by e.g. function and delegation based mixin patterns.

Thus, one first needs to figure out which parts of a composable object system should build the 'is a' relationship and which parts are behavior ('can do' / 'has a') that might get reused by/at different parts/levels of the object system.

The OP already has done this work. The example code that will be provided below is just going to introduce different function based mixin patterns via providing implementations of some behaviors like withLivingBeingBasics and withFlyingAbility ... with the first mixin covering the OP's Living_being class and the second one covering Flying_object.

For better code reuse there are 2 additional mixins, withExposeClassName and withExposeVector that, for the reason of demonstration, compose into the withExposeClassNameAndVector mixin compound.

From this possible base setup, that follows the OP's example, one could continue with shaping the classes.

With the Animal class one is free of choosing where mixin composition shall take place. Following the provided original example, composition at class/prototype level is the better option instead of applying the behavior at construction/instantiation time from within the constructor.

After applying withExposeClassNameAndVector and withLivingBeingBasics to Animal.prototype, any animal instance via prototypal delegation is capable of being invoked at getClassName and getVector as well as at metabolize and reproduce. Thus, at this point, both mixin composition (explicit delegation via call to prototype) and inheritance (automatically running delegation via prototype chain) take place.

The Fish class is implemented easily. As with the OP's example one just follows a simplified inheritance pattern via Fish.prototype = new Animal;. In addition, as already featured with the Animal base class, a class' name gets assigned to its prototype object.

Bird repeats the base patterns of Fish with the exception of featuring its own vector property that now is three dimensional instead of the otherwise prototypal two dimensional one. And since a common bird is supposed to somehow feature flying behavior Bird.prototype has to acquire it from withFlyingAbility.

Following still the OP's example, Ufo also needs to have flying ability. And like a bird, an ufo too has to feature an own three dimensional vector property. Thus the property gets assigned at construction/instantiation time and all the needed behavior gets applied from withExposeClassNameAndVector and from withFlyingAbility to Ufo.prototype.

The provided approach intends to prove that ... the most atomar mixin implements exactly one behavior, ... mixins are not necessarily supposed to introduce state but a behavior might operate upon state, ... mixins can be composed from other mixins, ... they always get applied at object level, either to already existing objects or to the prototype object itself or at composition time from within a constructor function ...

var withLivingBeingBasics = (function () { // function based mixin
  function metabolize() { // implement beahvior once.
    console.log('every living being features some kind of metabolism.');
  }
  function reproduce() {  // implement beahvior once.
    console.log('every living being features some kind of reproduction.');
  }
  return function() {
    this.metabolize = metabolize; // assign shared code.
    this.reproduce = reproduce;   //
  }
}());

var withFlyingAbility = (function () {
  function liftOffAerodynamically() {
    this.vector.z = 5;
    console.log('flying needs some kind of aerodynamic lift.');
  }
  function monitorAltitudeDifference() {
    console.log('monitoring altitude difference : ', this.vector.z);
  }
  return function() {
    this.liftOffAerodynamically = liftOffAerodynamically;
    this.monitorAltitudeDifference = monitorAltitudeDifference;
  }
}());

var withExposeVector = (function () {
  function getVector() {
    console.log('vector : ', this.vector);
  }
  return function() {
    this.getVector = getVector;
  }
}());

var withExposeClassName = (function () {
  function getClassName() {
    console.log('Instance of... ', this.className);
  }
  return function() {
    this.getClassName = getClassName;
  }
}());

var withExposeClassNameAndVector = function () { // mixin compound.
  withExposeClassName.call(this);
  withExposeVector.call(this);
}


function Animal() {
//withLivingBeingBasics.call(this); // mixing in for the given example is …
//this.className = 'Animal';        // … better at **class**/prototype level.
  this.vector = {x: 0, y: 0};
}
// the many ways of augmenting the `prototype` ...
Object.assign(Animal.prototype, { className: 'Animal' });

//withExposeClassName.call(Animal.prototype);
//withExposeVector.call(Animal.prototype);
withExposeClassNameAndVector.call(Animal.prototype);
withLivingBeingBasics.call(Animal.prototype);


function Fish() {}
Fish.prototype = new Animal;
Object.assign(Fish.prototype, { className: 'Fish' });


function Bird() {
  this.vector = {x: 0, y: 0, z: 0};
}
Bird.prototype = new Animal;
Object.assign(Bird.prototype, { className: 'Bird' });

withFlyingAbility.call(Bird.prototype);


function Ufo() {
  this.vector = {x: 0, y: 0, z: 0};
}
Object.assign(Ufo.prototype, { className: 'Ufo' });

//withExposeClassName.call(Ufo.prototype);
//withExposeVector.call(Ufo.prototype);
withExposeClassNameAndVector.call(Ufo.prototype);
withFlyingAbility.call(Ufo.prototype);


var fish = new Fish;
var bird = new Bird;
var ufo = new Ufo;

console.log('(fish instanceof Animal) ? ', (fish instanceof Animal));
console.log('(fish instanceof Fish) ? ', (fish instanceof Fish));
fish.getClassName();
fish.metabolize();
fish.reproduce();
fish.getVector();

console.log('(bird instanceof Animal) ? ', (bird instanceof Animal));
console.log('(bird instanceof Bird) ? ', (bird instanceof Bird));
bird.getClassName();
bird.metabolize();
bird.reproduce();
bird.getVector();
bird.monitorAltitudeDifference();
bird.liftOffAerodynamically();
bird.monitorAltitudeDifference();

console.log('(ufo instanceof Ufo) ? ', (ufo instanceof Ufo));
ufo.getClassName();
ufo.getVector();
ufo.monitorAltitudeDifference();
ufo.liftOffAerodynamically();
ufo.monitorAltitudeDifference();
.as-console-wrapper { max-height: 100%!important; top: 0; }

For further reading on SO one might give the following related questions and solutions a chance ...

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37