一些程序员建议不要在Javascript中使用伪经典继承,但建议使用duck-typing并为每个对象提供一组功能.
有一个很好的例子说明如何做到这一点?我在下面有一个例子,但它一次只分配一个函数.我们可以为一个对象分配一组方法,例如我们可以设置一个OceanAnimal可以"游泳","潜水"和"上升"的原型,一个LandAnimal"跑","走路"和"跳跃"的原型",让一个对象继承一个或两个?(所以鱼对象可以继承或获得的能力OceanAnimal,和龟可以得到两者的功能OceanAnimal和LandAnimal?)
var yoyo = {
name: "Yoyo",
type: "turtle"
}
var simba = {
name: "Simba",
type: "lion"
}
var dolphy = {
name: "Dolphy",
type: "dolphin"
}
function swim(n) {
console.log("My name is", this.name, ", I am a", this.type, "and I just swam", n, "feet")
}
function run(n) {
console.log("My name is", this.name, ", I am a", this.type, "and I just ran", n, "feet")
}
Object.prototype.respondTo = function(method) {
return !!(this[method] && (typeof this[method] === "function"));
}
yoyo.swim = swim;
yoyo.swim(10);
dolphy.swim = swim;
dolphy.swim(80);
simba.run = run;
simba.run(200);
yoyo.run = run;
yoyo.run(2);
yoyo.walk = run;
yoyo.walk(1);
console.log(simba.respondTo("swim"));
console.log(simba.respondTo("run"));
console.log(simba.respondTo("walk"));
console.log(yoyo.respondTo("run"));
console.log(yoyo.respondTo("walk"));
console.log(yoyo.respondTo("fly"));
if(dolphy.respondTo("run")) {
dolphy.run(10);
}
if(dolphy.respondTo("swim")) {
dolphy.swim(10);
}
Run Code Online (Sandbox Code Playgroud)
输出:
My name is Yoyo , I am a turtle and I just swam 10 feet
My name is Dolphy , I am a dolphin and I just swam 80 feet
My name is Simba , I am a lion and I just ran 200 feet
My name is Yoyo , I am a turtle and I just ran 2 feet
My name is Yoyo , I am a turtle and I just ran 1 feet
false
true
false
true
true
false
My name is Dolphy , I am a dolphin and I just swam 10 feet
Run Code Online (Sandbox Code Playgroud)
Aad*_*hah 22
JavaScript中的函数是通用的.它们可以用作子例程,方法,构造函数,命名空间,模块等等.
人们建议不要在JavaScript中使用伪经典继承是因为它隐藏了JavaScript的真正威力.如果不是比对象更具表现力,那么函数就像表达一样.Alonzo Church已经证明了这一点,Lambda Calculus的作品是Turing Complete.
要直接回答你的问题,我将使用函数来创建a Turtle,a Lion和a Dolphin.然后,我将演示一个乌龟怎么是OceanAnimal和LandAnimal,狮子怎么只有一个LandAnimal海豚,以及如何只有一个OceanAnimal.我将通过解释什么是鸭子打字来得出结论.
首先让我们创建一个构造函数OceanAnimal:
function OceanAnimal() {
this.swim = function (n) {
return "I am " + this.name + ", the " + this.type +
", and I just swam " + n + " meters.";
};
}
Run Code Online (Sandbox Code Playgroud)
接下来我们将为以下内容创建构造函数LandAnimal:
function LandAnimal() {
this.walk = function (n) {
return "I am " + this.name + ", the " + this.type +
", and I just walked " + n + " meters.";
};
}
Run Code Online (Sandbox Code Playgroud)
好的.所以现在让我们创建一个构造函数Turtle:
Turtle.prototype.type = "turtle";
function Turtle(name) {
this.name = name;
LandAnimal.call(this);
OceanAnimal.call(this);
}
Run Code Online (Sandbox Code Playgroud)
这里发生了什么事?好的,我们想Turtle继承两者OceanAnimal和LandAnimal.因此,我们呼吁LandAnimal.call(this)和OceanAnimal.call(this).通过这种方式,我们使用OceanAnimal和LandAnimal构造函数作为mixins.因此,Turtle从两个继承OceanAnimal和LandAnimal没有实际成为类型OceanAnimal或LandAnimal.
要注意的另一件事是,我们正在设置type的属性prototype的Turtle,而不是在它里面.这是因为type所有海龟都是一样的.因此它是共享的.在name另一方面每个龟的可能会发生变化,因此它的设置构造的内部.
现在让我们类似地创建一个构造函数Lion:
Lion.prototype.type = "lion";
function Lion(name) {
this.name = name;
LandAnimal.call(this);
}
Run Code Online (Sandbox Code Playgroud)
既然Lion是LandAnimal我们只在LandAnimal构造函数中混合.
同样的Dolphin:
Dolphin.prototype.type = "dolphin";
function Dolphin(name) {
this.name = name;
OceanAnimal.call(this);
}
Run Code Online (Sandbox Code Playgroud)
现在我们已经创建了所有构造函数,让我们创建一只乌龟,一只狮子和一只海豚:
var yoyo = new Turtle("Yoyo");
var simba = new Lion("Simba");
var dolphy = new Dolphin("Dolphy");
Run Code Online (Sandbox Code Playgroud)
Awww,现在我们让他们自由:
alert(yoyo.walk(10));
alert(yoyo.swim(30)); // turtles are faster in the water
alert(simba.walk(20));
alert(dolphy.swim(20));
Run Code Online (Sandbox Code Playgroud)
哈哈.那很有趣.就个人而言,我最爱yoyo.
好的,那么什么是鸭子打字?我们知道yoyo是a OceanAnimal和a LandAnimal.但是,如果我们这样做,yoyo instanceof OceanAnimal或者yoyo instanceof LandAnimal它返回false.什么?
Turtle是一个OceanAnimal和一个LandAnimal!Turtle.OceanAnimal,如果它走了,那么它就是一个LandAnimal.因此,由于JavaScript是这样的派对,我们必须创建自己的测试来检查一个对象是否是一个OceanAnimal,如果它是一个LandAnimal.
让我们从OceanAnimal:
function isOceanAnimal(object) {
if (typeof object !== "object") return false;
if (typeof object.swim !== "function") return false;
return true;
}
Run Code Online (Sandbox Code Playgroud)
同样,对于LandAnimal:
function isLandAnimal(object) {
if (typeof object !== "object") return false;
if (typeof object.walk !== "function") return false;
return true;
}
Run Code Online (Sandbox Code Playgroud)
所以,现在我们可以用isOceanAnimal(yoyo)代替yoyo instanceof OceanAnimal,并isLandAnimal(yoyo)代替yoyo instanceof LandAnimal; 这两个功能都将true归功于我们心爱的yoyo.好极了!
这是在JavaScript中输入duck的简单示例.总结:
当我看到一只像鸭子一样散步,像鸭子一样游泳,像鸭子一样呱呱叫的鸟儿时,我称这只鸟为鸭子.
同理:
当我看到一种像海洋动物一样游动的动物时,我称这种动物为海洋动物; 当我看到一只像陆地动物一样行走的动物时,我称这种动物为陆地动物.
编辑:你可以在这里看到上面的代码:http://jsfiddle.net/aaditmshah/X9M4G/
为什么不使用duck-typing计划来扩展组件和依赖注入,而不是尝试子类化?
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"
Run Code Online (Sandbox Code Playgroud)
请注意,plane很高兴使用相同的构造函数duck.
他们甚至不需要构造函数.
工厂也可以工作.
DuckTyping的观点是对象构造不是使用该对象的程序的关注点.
只要它具有相同的方法/属性名称,程序就会使用它们,而不管子/超/静态继承.
编辑: 添加了示例组件
这里有几个不同的想法,我在一起.所以一次一个点:
一,鸭子打字的基本前提:
// 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();
Run Code Online (Sandbox Code Playgroud)
目标是编写无需检查它是什么类型的对象的函数:
function duckDraw (array) { array.forEach(function (obj) { obj.draw(); }); }
duckDraw([ sheriff, cartoonist, graphicsCard ]);
Run Code Online (Sandbox Code Playgroud)
所以,如果你已经有了一个sea_turtle,海豚,鲸鱼,潜艇和鲦鱼,你的程序不必关心的差异如何,他们游泳.它只关心他们可以.swim();.每个项目都可以担心自己,以及它需要做的特殊方式.
鸭打字
接下来是依赖注入.在某种程度上,依赖注入也使用duck-typing,但是在你的类的内部,而不是在外面(或者如果你像我做的那样,它从内部开始,然后允许鸭子打字在外面,以及).
可以这样想:不是一个人继承某些东西,而是将它交给他们.
如果你有一个soldier,一个sniper一个plane和tank,每一个都需要一个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 () {};
};
Run Code Online (Sandbox Code Playgroud)
所以现在我们有各种各样的枪......你可能会认为,因为它们有相同的功能名称,而且它们都处理子弹,所以你可以尝试子类......但是你不要不需要 - 这些枪在射击或重装时都不会做同样的事情......所以你最终会用其他语言写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() );
Run Code Online (Sandbox Code Playgroud)
所以现在我们已经把这些课程称为他们的枪 - 他们不关心什么样的枪,它只是起作用,因为枪知道枪是如何工作的,战斗员知道如何开枪,以及程序知道如何告诉战斗员开火.
但是如果你仔细观察一下,现在每个战斗员的内部代码是100%相同的.
那么为什么不只是有一个'战斗',你可以提供专门的组件?
var Combatant = function (gun) {
this.currentGun = gun;
this.inventory = {
guns : [ gun ]
};
this.attack = function () { this.currentGun.fire(); };
};
var soldier = new Combatant( new Rifle() );
Run Code Online (Sandbox Code Playgroud)
所以构造函数的内部是鸭子类型gun,如果你有战斗员的不同类,并且每个类都有一个fire方法,那么你也可以在游戏逻辑中对你的单位进行躲避.
最终,构造函数只能容纳模块:一个用于处理射击的模块,一个用于处理地面移动的模块,一个用于绘制的模块,一个用于玩家控制的模块等等......构造函数除了将这些块接触之外不需要做任何事情彼此之间,你可以通过给他们不同种类的枪支,不同种类的运动或不同种类的健康来使单位特殊,这些运动在内部有不同的运作方式,但具有相同的属性和方法名称供公众使用.
所以你有这两个功能:
function make ( props ) {
var obj = Object.create( {} );
Object.keys( props ).forEach(function ( key ) {
obj[ key ] = props[ key ];
});
return obj;
};
function addMethods ( obj, methods ) {
var proto = Object.getPrototypeOf( obj );
Object.keys( methods ).forEach(function ( key ) {
proto[ key ] = methods[ key ];
});
}
Run Code Online (Sandbox Code Playgroud)
用法:
var simba = make({
name: "Simba",
type: "lion"
});
addMethods( simba, {
swim: function () {},
run: function () {}
});
addMethods( simba, {
hunt: function () {},
kill: function () {}
});
Run Code Online (Sandbox Code Playgroud)
现场演示: http: //jsfiddle.net/UETVc/
| 归档时间: |
|
| 查看次数: |
7140 次 |
| 最近记录: |