与其尝试子类化,不如使用鸭子类型倡议来扩展组件和依赖注入?
var Duck = function (flyable, movable, speakable) {
this.speak = speakable.speak;
this.fly = flyable.fly;
this.position = movable.position;
flyable.subscribe("takeoff", movable.leaveGround);
flyable.subscribe("land", movable.hitGround);
}
var duck = new Duck(new BirdFlier(), new BirdWalker(), new Quacker());
var plane = new Duck(new VehicleFlier(), new Drivable(), new Radio());
duck.speak(); // "quack"
plane.speak(); // "Roger"
请注意,plane
它愉快地使用与duck
.
他们甚至一开始就不需要构造函数。
工厂也可以。
DuckTyping 的重点是对象构造不是使用对象的程序所关心的。
只要它具有相同的方法/属性名称,程序就会使用它们,而不管子/超/静态继承。
编辑: 添加示例组件
这里有几个不同的想法,我将它们结合在一起。所以一次一个点:
一、duck-typing的基本前提:
// the basic nature of duck-typing
var sheriff = {
gun : {
aim : function () { /* point gun somewhere */ },
bullets : 6,
fire : function () {
if (this.bullets === 0) { return; }
this.bullets -= 1;
/* ... et cetera */
}
},
draw : function () {
this.gun.aim();
this.gun.fire();
}
},
cartoonist = {
pen : { scribble : function () { /* ... */ } },
draw : function () { this.pen.scribble(); }
},
graphicsCard = {
pixels : [ /* ... */ ],
screen : { /* ... */ },
draw : function () {
pixels.forEach(function (pixel) { screen.draw(pixel); });
}
};
// duck-typing at its finest:
sheriff.draw();
cartoonist.draw();
graphicsCard.draw();
目标是编写不需要检查它是什么类型的对象的函数:
function duckDraw (array) { array.forEach(function (obj) { obj.draw(); }); }
duckDraw([ sheriff, cartoonist, graphicsCard ]);
因此,如果您有海龟、海豚、鲸鱼、潜艇和小鱼,您的程序就不必关心它们游泳方式的差异。它只关心他们都可以.swim();
。每个项目都可以担心自己,以及它做它需要做的特殊方式。
鸭子打字
接下来是依赖注入。在某种程度上,依赖注入也使用鸭子类型,但在你的类内部,而不是外部(或者如果你像我在上面那样做,它从内部开始,然后允许鸭子类型在外面也是如此)。
可以这样想:与其让一个人继承某些东西,不如把它交给他们。
如果您有 a soldier
、 a sniper
aplane
和 a tank
,则每个都需要 a gun
。与其尝试子类化以便他们都能开火……为什么不制造不同种类的枪来满足您的需求,并把他们需要的一切都交给他们呢?
var Rifle = function () {
this.reload = function () {};
this.fire = function () { /* ... */ };
},
SniperRifle = function () {
this.reload = function () {};
this.fire = function () {};
},
MachineGun = function () {
this.reload = function () {};
this.fire = function () {};
},
Cannon = function () {
this.reload = function () {};
this.fire = function () {};
};
所以现在我们有了不同种类的枪......你可能会认为因为它们有相同的函数名称,而且它们都处理子弹,所以你可以尝试子类......但你没有不需要 - 这些枪在开火或重装时都不会做同样的事情......所以你最终会用其他语言写overrides
一个方法/属性,这将是无用的。abstract virtual
所以现在我们已经有了它们,我们可以看到我们如何“注入”它们,以及这对我们有什么作用:
var Soldier = function (gun) {
this.currentGun = gun;
this.inventory = {
guns : [ gun ]
};
this.attack = function () { this.currentGun.fire(); };
};
var Sniper = function (gun) {
this.currentGun = gun;
this.inventory = {
guns : [ gun ]
};
this.attack = function () { this.currentGun.fire(); };
};
var Plane = function (gun) {
this.currentGun = gun;
this.inventory = {
guns : [ gun ]
};
this.attack = function () { this.currentGun.fire(); };
};
var Tank = function (gun) {
this.currentGun = gun;
this.inventory = {
guns : [ gun ]
};
this.attack = function () { this.currentGun.fire(); };
};
var soldier = new Soldier( new Rifle() ),
sniper = new Sniper( new SniperRifle() ),
plane = new Plane( new MachineGun() ),
tank = new Tank( new Cannon() );
所以现在我们有了这些他们称之为枪的课程——他们不在乎什么样的枪,它只是工作,因为枪知道枪是如何工作的,战斗人员知道如何开枪,而且程序知道如何告诉战斗人员开火。
但如果你仔细观察,现在每个战斗员的内部代码都是 100% 相同的。
那么,为什么不拥有一个可以提供特殊组件的“战斗者”呢?
var Combatant = function (gun) {
this.currentGun = gun;
this.inventory = {
guns : [ gun ]
};
this.attack = function () { this.currentGun.fire(); };
};
var soldier = new Combatant( new Rifle() );
所以构造函数的内部是鸭式类型gun
,如果你有不同的战斗员类,并且每个类都有一个fire
方法,那么你也可以在游戏逻辑中对你的单位进行鸭式类型。
最终,构造函数将只保存模块:一个处理射击的模块,一个处理地面移动的模块,一个用于绘图的模块,一个用于玩家控制的模块,等等......构造函数不需要做任何事情,除了让各个部分接触彼此之间,您可以通过赋予它们不同种类的枪、不同种类的运动或不同种类的健康来使单位变得特别,它们在内部运作不同,但具有相同的属性和方法名称以供公众访问。