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.

11
  • 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. Commented Nov 20, 2017 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. Commented Nov 20, 2017 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 Commented Nov 20, 2017 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. Commented Nov 20, 2017 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? Commented Nov 20, 2017 at 19:27

3 Answers 3

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();
Sign up to request clarification or add additional context in comments.

2 Comments

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?
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.
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.

13 Comments

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.
Loading jQuery just to use $.extend seems like bad advice. Especially when Object.assign exists.
Object.assign() extend only enumerable properties.
@СергейПетрашко: True, I forgot about that. The point still stands. jQuery is way overkill for this, and frankly for most modern code.
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.
|
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 ...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.