JavaScript 多重继承的高效解决方案

JavaScript efficient solution for multi-inheritance

为什么这个问题不是重复的

这个答案javascript inheritance from multiple objects没有解决我的问题(尽管它已被标记为我之前问题的重复),因为它违反了不可扩展性DRY 原则。

为此,必须像这样手动引用每个方法:

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

如果我有几十个包含许多方法的 classes 怎么办?我真的应该在我的源代码中一遍又一遍地为每个 class 手动复制粘贴相同的参考吗?虽然此解决方案适用于非常少量的 classes,但它不适用于使用数十个或数百个 classes 的大规模应用程序。

我要解决的问题

我正在尝试实例化对象,这些对象必须使用 new 关键字继承 AnimalFlying_object 的所有属性和方法。

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

棘手的部分是 Animal 并且 Flying_object 不能有父子关系。

我知道 JavaScript 没有实现多重继承的本机方法,所以我发布这个问题是为了获得一些帮助,找到一个自定义的、可扩展的解决方案来解决这个问题。

代码示例和预期行为

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'

这就是我卡住的地方。如何让 Bird 继承 AnimalFlying_object ? 任何见解将不胜感激。

如果您需要从几个 类 继承,您可以使用 jquery 函数 $.extend({}); 从 Animal 和 Ufo 扩展原型 Bird。 示例 $.extend(Bird.prototype,Animal.prototype,UFO.prototype) 或者您可以创建自定义扩展 function.If 名称属性或函数具有相同的名称,它们将被重写。

我从文档中得到这个: 使用 Object.assign() extend only enumerable properties.The Object.assign() method only copies enumerable and own从源对象到目标对象的属性。它在源上使用 [[Get]],在目标上使用 [[Set]],因此它将调用 getter 和 setter。因此,它分配属性而不是仅仅复制或定义新属性。如果合并源包含吸气剂,这可能使其不适合将新属性合并到原型中。要将 属性 定义(包括它们的可枚举性)复制到原型中,应改用 Object.getOwnPropertyDescriptor() 和 Object.defineProperty()。

这是我在某个时候提出并放弃的可行解决方案,因为我认为可能有更好的解决方案。

@Mörre:我不确定这是你在评论中建议我做的:这就是你所说的对象组合吗?还是我这里全错了?

演示: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();

JavaScript不支持多重继承的概念。它也没有实现基于 mixin and/or 特征的组合的语法。但与前者不同的是,后者可以通过例如基于函数和委托的混合模式。

因此,首先需要弄清楚可组合对象系统的哪些部分应该建立 'is a' 关系,哪些部分是行为 ('can do' / 'has a') 可能会得到重用了 by/at 个不同的 parts/levels 对象系统。

OP 已经完成了这项工作。下面将提供的示例代码将通过提供一些行为的实现来引入不同的基于函数的混合模式,例如 withLivingBeingBasicswithFlyingAbility ...第一个混合覆盖 OP 的 Living_being class 和第二个涵盖 Flying_object.

为了更好的代码重用,还有 2 个额外的 mixin,withExposeClassNamewithExposeVector,出于演示的原因,它们组成了 withExposeClassNameAndVector mixin 化合物。

根据这个可能的基础设置,按照 OP 的示例,可以继续塑造 classes。

使用 Animal class 可以自由选择 mixin 组合发生的位置。按照提供的原始示例,class/prototype 级别的组合是更好的选择,而不是从构造函数中在 construction/instantiation 时间应用行为。

withExposeClassNameAndVectorwithLivingBeingBasics 应用到 Animal.prototype 之后,任何通过原型委托的动物实例都可以在 getClassNamegetVector 处被调用如 metabolizereproduce。因此,在这一点上,mixin 组合(通过 callprototype 的显式委托)和继承(通过原型链自动 运行 委托)都发生了。

Fish class 很容易实现。与 OP 的示例一样,仅通过 Fish.prototype = new Animal; 遵循简化的继承模式。此外,正如 Animal 基础 class 的特点,一个 class' 名称被分配给它的 prototype 对象。

Bird 重复 Fish 的基本模式,除了它自己的 vector 属性 是三维的,而不是其他原型的二维的.而且由于一只普通的鸟应该以某种方式具有飞行行为 Bird.prototype 必须从 withFlyingAbility.

获得它

还是按照OP的例子,Ufo也需要有飞行能力。和鸟一样,不明飞行物也必须具有自己的三维 vector 属性。因此 属性 在 construction/instantiation 时被分配,并且所有需要的行为都从 withExposeClassNameAndVector 和从 withFlyingAbility 应用到 Ufo.prototype.

提供的方法旨在证明...最原子的 mixin 恰好实现了一种行为,...mixin 不一定应该引入状态,但行为可能对状态进行操作,...可以组合 mixin来自其他mixins,...它们总是在对象级别应用,要么应用到已经存在的对象,要么应用到prototype对象本身,或者在构造函数中的组合时应用...

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

为了进一步阅读 SO,可能会给以下相关问题和解决方案一个机会 ...

  • Traits in javascript