JavaScript高效的多继承解决方案

时间:2017-11-20 19:04:08

标签: javascript oop inheritance mixins composition

为什么这个问题不重复

这个答案javascript inheritance from multiple objects确实解决了我的问题(虽然它已被标记为我之前的问题的副本),因为它不具有可扩展性,因为它违反了DRY原则。 / p>

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

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

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

我试图解决的问题

enter image description here

我尝试使用Animal关键字实例化必须继承Flying_objectnew的所有属性和方法的对象。

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

棘手的部分是AnimalFlying_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? 任何见解都会得到很大的启发。

3 个答案:

答案 0 :(得分:0)

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

我从文档中得到了这个:使用Object.assign()只扩展了可枚举的属性.Object.assign()方法只将源对象的可枚举和自己的属性复制到目标对象。它在源上使用[[Get]],在目标上使用[[Set]],因此它将调用getter和setter。因此,它分配属性而不仅仅是复制或定义新属性。如果合并源包含getter,这可能使它不适合将新属性合并到原型中。为了将属性定义(包括它们的可枚举性)复制到原型中,应该使用Object.getOwnPropertyDescriptor()和Object.defineProperty()。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

JavaScript不支持多重继承的概念。它也没有实现基于mixin和/或trait的组合的语法。但与前者不同,后者可以通过例如基于功能和授权的mixin模式。

因此,首先需要弄清楚可组合对象系统的哪些部分应该构建'是'关系,哪些部分是行为('可以做'/'有''),可能会被/在不同的地方重用对象系统的部分/级别。

OP已经完成了这项工作。下面将提供的示例代码将通过提供诸如withLivingBeingBasicswithFlyingAbility等某些行为的实现来介绍基于不同功能的mixin模式...第一个mixin覆盖OP的{{1} } class和第二个覆盖Living_being

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

从这个可能的基础设置开始,在OP的例子之后,可以继续对类进行整形。

使用withExposeClassNameAndVector类,可以选择mixin组成的位置。按照提供的原始示例,类/原型级别的组合是更好的选择,而不是在构造函数中的构造/实例化时间应用行为。

AnimalwithExposeClassNameAndVector应用于withLivingBeingBasics后,通过原型授权的任何动物实例都可以在Animal.prototypegetClassName以及getVectormetabolize。因此,此时,mixin组合(通过reproduce显式委派到call)和继承(通过原型链自动运行委派)都会发生。

prototype类很容易实现。与OP的示例一样,只需通过Fish遵循简化的继承模式。此外,正如已经使用Fish.prototype = new Animal;基类一样,类名称将被分配给其Animal对象。

prototype重复Bird的基本模式,但具有自己的Fish属性除外,该属性现在是三维的,而不是原始的二维属性。并且由于常见的鸟类应该以某种方式表现出飞行行为vector必须从Bird.prototype获取它。

继OP的例子之后,withFlyingAbility也需要具备飞行能力。就像鸟一样,不明飞行物也必须具有自己的三维Ufo属性。因此,属性在构造/实例化时间分配,并且所有需要的行为都从vectorwithExposeClassNameAndVectorwithFlyingAbility应用。

提供的方法旨在证明......最常见的mixin实现了一种行为,... mixins不一定要引入状态,但行为可能在状态下运行,... mixins可以由其他组成mixins,...它们总是应用于对象级别,既可以应用于已存在的对象,也可以应用于Ufo.prototype对象本身,或者在构造函数中的组合时间...

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

有关SO的进一步阅读,可能会给出以下相关问题和解决方案......