我已使用this series中的前两个视频来了解一些基本的OOP概念。
最近,我主要在Node中写作,所以我在前端和后端使用原型继承。但是,这些教程使用Java展示了OOP概念。 Java是一种严格类型的语言,它使用经典继承。
这个问题涉及经典和原型继承,但是以不同的方式。
这个问题有点难以理解,所以我将使用一个例子:
我创建了一个名为 animal 的超类。然后我创建了两个动物子类:马和驴。现在我的程序需要两个子类的混合。创建 mule 实际上似乎有点棘手。
起初答案似乎很明显;创建一个独立的骡子类。但这种打败了OOP的目的。当我已经具有这些特征时创建一个新的子类是违反DRY原则。
要确认这是创建 mule 的合适方式,我问自己两个问题:
1)骡子是马吗?
2)骡子是驴吗?
答案似乎是一种响亮的那种,倾向于是。
我完全不知道如何通过经典继承来实现这一目标。我无法想出我认为具有接口或抽象类的" good" 解决方案。
在使用原型继承之类的JavaScript语言中,我可能"通过仅下拉方法和实例变量来选择性地繁殖" 适用于 mule 。然而,这似乎与创建一个全新的子类非常接近。
在经典和原型继承中,"正确" 处理此问题的方法是什么?
答案 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();
}
}
答案 1 :(得分:0)
起初答案似乎很明显;创建一个独立的骡子类。 但这种打败了OOP的目的。创建一个新的子类 当我已经具备这些特征时,就违反了DRY原则。
分解可能有助于实现干旱目标。
显然不应该继承的每个行为/角色都可以被视为实现为 mixin 或 trait 。 因此,通过合成,他们在类级别的不同位置重用代码更容易,更优雅。
对于JavaScript,只有委托。一方面的继承由隐式委托自动化支持
通过原型链,通过call
或apply
明确委派功能来实现组合。
这使事情变得更容易,因为只需要处理对象/实例和方法/函数对象。类/继承
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上给出的一些列出的答案,这些答案也与此主题相关。