如何处理两个类似子类的继承?

时间:2016-02-09 03:37:18

标签: class oop inheritance composition traits

我已使用this series中的前两个视频来了解一些基本的OOP概念。

最近,我主要在Node中写作,所以我在前端和后端使用原型继承。但是,这些教程使用Java展示了OOP概念。 Java是一种严格类型的语言,它使用经典继承

这个问题涉及经典和原型继承,但是以不同的方式。

这个问题有点难以理解,所以我将使用一个例子:

我创建了一个名为 animal 的超类。然后我创建了两个动物子类:。现在我的程序需要两个子类的混合。创建 mule 实际上似乎有点棘手。

起初答案似乎很明显;创建一个独立的骡子类。但这种打败了OOP的目的。当我已经具有这些特征时创建一个新的子类是违反DRY原则

要确认这是创建 mule 的合适方式,我问自己两个问题:

1)骡子是吗?

2)骡子是吗?

答案似乎是一种响亮的那种,倾向于

我完全不知道如何通过经典继承来实现这一目标。我无法想出我认为具有接口或抽象类的" good" 解决方案。

在使用原型继承之类的JavaScript语言中,我可能"通过仅下拉方法和实例变量来选择性地繁殖" 适用于 mule 。然而,这似乎与创建一个全新的子类非常接近。

经典原型继承中,"正确" 处理此问题的方法是什么?

2 个答案:

答案 0 :(得分:1)

您正在寻找的概念是特质(您实际上提到过它)。我将使用一个不同的例子,我觉得更合适:

trait Engine {
    public function startEngine() {
        echo 'Vrooom';
    }
}

trait Saddle {
    public function rideOnSaddle() {
        echo 'I feel the wind';
    }
}

interface Vehicle {
    public function go();
}

class Car extends Vehicle {
    use Engine;

    public function go() {
        echo $this->startEngine();
    }
}

class Bike extends Vehicle {
    use Saddle;

    public function go() {
        echo $this->rideOnSaddle();
    }
}

class Motorcycle extends Vehicle {
    use Engine;
    use Saddle;

    public function go() {
        echo $this->startEngine();
        echo $this->rideOnSaddle(); 
    }
}

进一步阅读:Traits in PHPTraits in Javascript

答案 1 :(得分:0)

  

起初答案似乎很明显;创建一个独立的骡子类。   但这种打败了OOP的目的。创建一个新的子类   当我已经具备这些特征时,就违反了DRY原则。

分解可能有助于实现干旱目标。

显然不应该继承的每个行为/角色都可以被视为实现为 mixin trait 。 因此,通过合成,他们在类级别的不同位置重用代码更容易,更优雅。

对于JavaScript,只有委托。一方面的继承由隐式委托自动化支持 通过原型链,通过callapply明确委派功能来实现组合。

这使事情变得更容易,因为只需要处理对象/实例和方法/函数对象。类/继承 JavaScript中的部分由构造函数和每个构造函数的prototype或(蓝图 - )对象覆盖 作为原型传递给Object.create。工厂将在提供API和隐藏首选实现方面很有用 上述方法。

尽管如此,核心原则仍未触及它们......处理a)对象功能对象 b)继承和c)组合

以下提供的示例因此仅选择ECMAScript-3功能和构造函数。从那里它可以 可轻松转移(/转换)为 class 语法或Object.create

OP的例子很好,因为骡子既不是马也不是驴。它仍然属于属 Equus 但属于 它自己的染色体对不同于马或驴。然而它具有行为和可见标记 他们两个。因此,继承是通过 Equus 实现的。其他特定的行为和外观 对两种物种中的每一种都是通用的,或者将它们混合成骡子。

JavaScript中基于函数的mixins / traits / talent总是应用于对象/实例,因此甚至可以继承的行为 通过原型链,可以收集到这样的功能中,如果必要/适当的话,可以再次应用于原型对象。

以下示例使用此技术并对其进行评论,以演示DRY-ness和代码重用 在这两个代表级别的JavaScript。

var
  INITIAL_STATES_CONFIG = {
    equus: {
      specifics: {
          type: "equus"
      }/*,
      generics: {
      }*/
    },
    horse: {
      specifics: {
          type: "horse"
      }/*,
      generics: {
      }*/
    },
    donkey: {
      specifics: {
          type: "donkey"
      }/*,
      generics: {
      }*/
    },
    mule: {
      specifics: {
          type: "mule"
      }/*,
      generics: {
      }*/
    }
  };



function withToeltGait() { // function based mixin/trait/talent.
  this.toelt = function () {
    return "... tölt ...";
  };
  return this;
}


function withEquusGenerics(/* state */) { // function based mixin/trait/talent composite.
  var
    equus = this;

  // implementation of equus generics.

  equus.walk = function () {
    return "... walk ...";
  };
  equus.trot = function () {
    return "... trot ...";
  };
  equus.gallop = function () {
    return "... gallop ...";
  };
  withToeltGait.call(equus); // composition: use/apply specific equus trait.

  return equus;
}
function withEquusSpecifics(state ) { // function based mixin/trait/talent.
  var
    equus = this;

  // implementation of equus specifics.

  equus.valueOf = function () {
    return Object.assign({}, state);
  };
  equus.toString = function () {
    return JSON.stringify(state);
  };

  return equus;
}

function Equus(state) { // constructor, kept generic via mixin/trait/talent composition.
  state = ((typeof state === 'object') && state) || {};
  var
    equus = this;

  withEquusSpecifics.call(equus, state); // composition: use/apply specific equus trait.

  return equus;
}
// equus inheritance via trait based generic equus composite object.
Equus.prototype = withEquusGenerics.call(new Equus/*, state */);


console.log("Equus.prototype.valueOf() : ", Equus.prototype.valueOf());
console.log("Equus.prototype.toString() : ", Equus.prototype.toString());

console.log("Equus.prototype.walk() : ", Equus.prototype.walk());
console.log("Equus.prototype.trot() : ", Equus.prototype.trot());
console.log("Equus.prototype.toelt() : ", Equus.prototype.toelt());
console.log("Equus.prototype.gallop() : ", Equus.prototype.gallop());

console.log("\n");


var equus = new Equus(INITIAL_STATES_CONFIG.equus.specifics);

console.log("equus.valueOf() : ", equus.valueOf());
console.log("equus.toString() : ", equus.toString());

console.log("equus instanceof Equus ? ", (equus instanceof Equus));


console.log("+++ +++ +++\n\n");



function withHorseGenerics(/* state */) { // function based mixin/trait/talent.
  /*
    implementation of horse generics.
  */
  var
    horse = this;

  // almost all of today's horse breeds lost theirs genetic tölt predisposition.
  horse.toelt = function () {};

  horse.alwaysAlertedAndFleeQuickly = function () {
    return "... always alerted and flee quickly ...";
  };
  return horse;
}
function withHorseSpecifics(/* state */) { // function based mixin/trait/talent.
  /*
    implementation of horse specifics.
  */
  return this;
}

function Horse(state) { // constructor, kept generic via mixin/trait/talent composition.
  state = ((typeof state === 'object') && state) || {};
  var
    horse = this;

  Equus.call(horse, state);                   // - fulfilling proper equus composition.
  withHorseSpecifics.call(horse/*, state */); // - composition: use/apply specific horse trait.

  return horse;
}
// equus inheritance together with generic horse trait composition.
Horse.prototype = withHorseGenerics.call(new Equus/*, state */);


var horse = new Horse(INITIAL_STATES_CONFIG.horse.specifics);

console.log("horse.valueOf() : ", horse.valueOf());
console.log("horse.toString() : ", horse.toString());

console.log("horse instanceof Horse ? ", (horse instanceof Horse));
console.log("horse instanceof Equus ? ", (horse instanceof Equus));

console.log("horse.walk() : ", horse.walk());
console.log("horse.trot() : ", horse.trot());
console.log("horse.toelt() : ", horse.toelt());
console.log("horse.gallop() : ", horse.gallop());

console.log("horse.alwaysAlertedAndFleeQuickly() : ",
  (horse.alwaysAlertedAndFleeQuickly && horse.alwaysAlertedAndFleeQuickly())
);
console.log("horse.beAttentiveCalculateAndRatherFight() : ",
  (horse.beAttentiveCalculateAndRatherFight && horse.beAttentiveCalculateAndRatherFight())
);
console.log("\n");


var toeltingHorse = new Horse(INITIAL_STATES_CONFIG.horse.specifics);
withToeltGait.call(toeltingHorse);

console.log("toeltingHorse.valueOf() : ", toeltingHorse.valueOf());
console.log("toeltingHorse instanceof Horse ? ", (toeltingHorse instanceof Horse));
console.log("toeltingHorse instanceof Equus ? ", (toeltingHorse instanceof Equus));
console.log("toeltingHorse.toelt() : ", toeltingHorse.toelt());

console.log("+++ +++ +++\n\n");



function withDonkeyGenerics(/* state */) { // function based mixin/trait/talent.
  /*
    implementation of donkey generics.
  */
  var
    donkey = this;

  // donkey breeds, as far as I know, still have the genetic
  // predisposition for tölt, but they need to get trained.
  //
  // donkey.toelt = function () {};

  donkey.beAttentiveCalculateAndRatherFight = function () {
    return "... be attentive, calculate and rather fight ...";
  };
  return donkey;
}
function withDonkeySpecifics(/* state */) { // function based mixin/trait/talent.
  /*
    implementation of donkey specifics.
  */
  return this;
}

function Donkey(state) { // constructor, kept generic via mixin/trait/talent composition.
  state = ((typeof state === 'object') && state) || {};
  var
    donkey = this;

  Equus.call(donkey, state);                    // - fulfilling proper equus composition.
  withDonkeySpecifics.call(donkey/*, state */); // - composition: use/apply specific donkey trait.

  return donkey;
}
// equus inheritance together with generic donkey trait composition.
Donkey.prototype = withDonkeyGenerics.call(new Equus/*, state */);


var donkey = new Donkey(INITIAL_STATES_CONFIG.donkey.specifics);

console.log("donkey.valueOf() : ", donkey.valueOf());
console.log("donkey.toString() : ", donkey.toString());

console.log("donkey instanceof Donkey ? ", (donkey instanceof Donkey));
console.log("donkey instanceof Equus ? ", (donkey instanceof Equus));

console.log("donkey.walk() : ", donkey.walk());
console.log("donkey.trot() : ", donkey.trot());
console.log("donkey.toelt() : ", donkey.toelt());
console.log("donkey.gallop() : ", donkey.gallop());

console.log("donkey.alwaysAlertedAndFleeQuickly() : ",
  (donkey.alwaysAlertedAndFleeQuickly && donkey.alwaysAlertedAndFleeQuickly())
);
console.log("donkey.beAttentiveCalculateAndRatherFight() : ",
  (donkey.beAttentiveCalculateAndRatherFight && donkey.beAttentiveCalculateAndRatherFight())
);
console.log("+++ +++ +++\n\n");



function withMuleGenerics(/* state */) { // function based mixin/trait/talent composite.
  /*
    implementation of mule generics.
  */
  var
    mule = this;

  withDonkeyGenerics.call(mule/*, state */);  // composition: use/apply generic donkey trait.
  /*
    add or delete mule generic properties afterwards.
  */

  withHorseGenerics.call(mule/*, state */);   // composition: use/apply generic horse trait.
  /*
    add or delete mule generic properties afterwards.
  */

  // a mules genetic predisposition for tölt is inherited by its mother horse.
  // therefore via calling `withHorseGenerics` this trait gets disabled too by default.

  // when facing danger a mule behaves like a donkey; it rather will fight than flee.
  mule.alwaysAlertedAndFleeQuickly = function () {};

  return mule;
}
function withMuleSpecifics(/* state */) { // function based mixin/trait/talent composite.
  /*
    implementation of mule specifics.
  */
  var
    mule = this;

  withDonkeySpecifics.call(mule/*, state */); // composition: use/apply specific donkey trait.
  /*
    add or delete mule specific properties afterwards.
  */

  withHorseSpecifics.call(mule/*, state */);  // composition: use/apply specific horse trait.
  /*
    add or delete mule specifics properties afterwards.
  */

  return mule;
}

function Mule(state) { // constructor, kept generic via mixin/trait/talent composition.
  state = ((typeof state === 'object') && state) || {};
  var
    mule = this;

  Equus.call(mule, state);                  // - fulfilling proper equus composition.
  withMuleSpecifics.call(mule/*, state */); // - composition: use/apply specific mule trait.

  return mule;
}
// equus inheritance together with generic mule trait composition.
Mule.prototype = withMuleGenerics.call(new Equus/*, state */);


var mule = new Mule(INITIAL_STATES_CONFIG.mule.specifics);

console.log("mule.valueOf() : ", mule.valueOf());
console.log("mule.toString() : ", mule.toString());

console.log("mule instanceof Mule ? ", (mule instanceof Mule));
console.log("mule instanceof Equus ? ", (mule instanceof Equus));

console.log("mule instanceof Donkey ? ", (mule instanceof Donkey));
console.log("mule instanceof Horse ? ", (mule instanceof Horse));

console.log("mule.walk() : ", mule.walk());
console.log("mule.trot() : ", mule.trot());
console.log("mule.toelt() : ", mule.toelt());
console.log("mule.gallop() : ", mule.gallop());

console.log("mule.alwaysAlertedAndFleeQuickly() : ",
  (mule.alwaysAlertedAndFleeQuickly && mule.alwaysAlertedAndFleeQuickly())
);
console.log("mule.beAttentiveCalculateAndRatherFight() : ",
  (mule.beAttentiveCalculateAndRatherFight && mule.beAttentiveCalculateAndRatherFight())
);
console.log("\n");


var toeltingMule = new Mule(INITIAL_STATES_CONFIG.mule.specifics);
withToeltGait.call(toeltingMule);

console.log("toeltingMule.valueOf() : ", toeltingMule.valueOf());
console.log("toeltingMule instanceof Mule ? ", (toeltingMule instanceof Mule));
console.log("toeltingMule instanceof Equus ? ", (toeltingMule instanceof Equus));
console.log("toeltingMule.toelt() : ", toeltingMule.toelt());

console.log("+++ +++ +++\n\n");

旁注 - 关于JavaScript中的Mixins / Traits / Talents函数的推荐资源

此外,我建议阅读我在SO上给出的一些列出的答案,这些答案也与此主题相关。