10

一部のプログラマーは、Javascript で疑似古典的継承を使用しないようアドバイスしますが、ダックタイピングを使用して各オブジェクトに一連の機能を与えることをアドバイスします。

それがどのように行われるかの良い例はありますか?以下に例を示しますが、関数は一度に 1 つしか割り当てられません。メソッドのグループ全体をオブジェクトに割り当てることはできますか? たとえば、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);
}

出力:

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
4

3 に答える 3

26

JavaScript の関数は用途が広いです。これらは、サブルーチンメソッドコンストラクター名前空間モジュールなどとして使用できます。

JavaScript で疑似古典的継承を使用しないよう人々がアドバイスする理由は、JavaScript の真の力が隠されているからです。関数は、オブジェクトよりも表現力があるとは言えませんが、同じくらい表現力があります。これは、チューリング完全であるLambda Calculusの作品であるAlonzo Churchによって証明されています。

あなたの質問に直接答えるために、関数を使用してTurtle、 、Lionおよびを作成します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.";
    };
}

次に、 a のコンストラクターを作成しますLandAnimal

function LandAnimal() {
    this.walk = function (n) {
        return "I am " + this.name + ", the " + this.type +
               ", and I just walked " + n + " meters.";
    };
}

大丈夫。それでは、 a のコンストラクターを作成しましょうTurtle

Turtle.prototype.type = "turtle";

function Turtle(name) {
    this.name = name;
    LandAnimal.call(this);
    OceanAnimal.call(this);
}

ここで何が起こっているのですか?さて、 と の両方から継承したいとTurtle思います。したがって、 andを呼び出しています。このように、およびコンストラクターをmixinとして使用しています。したがって、実際にor型になることなく、 andの両方から継承します。OceanAnimalLandAnimalLandAnimal.call(this)OceanAnimal.call(this)OceanAnimalLandAnimalTurtleOceanAnimalLandAnimalOceanAnimalLandAnimal

もう 1 つの注目すべき点は、typeプロパティを内部でprototypeはなく、上に設定していることです。Turtleこれはtype、すべてのタートルで同じだからです。このように共有されます。一方name、各タートルの は異なる場合があるため、コンストラクター内で設定されます。

同様に、 a のコンストラクターを作成しましょうLion

Lion.prototype.type = "lion";

function Lion(name) {
    this.name = name;
    LandAnimal.call(this);
}

Lionであるため、コンストラクターLandAnimalでのみミックスします。LandAnimal

同様に a Dolphin:

Dolphin.prototype.type = "dolphin";

function Dolphin(name) {
    this.name = name;
    OceanAnimal.call(this);
}

すべてのコンストラクターを作成したので、カメ、ライオン、イルカを作成しましょう。

var yoyo = new Turtle("Yoyo");
var simba = new Lion("Simba");
var dolphy = new Dolphin("Dolphy");

Awww、ではそれらを解放しましょう:

alert(yoyo.walk(10));
alert(yoyo.swim(30));   // turtles are faster in the water
alert(simba.walk(20));
alert(dolphy.swim(20));

ハハ。それは楽しかった。個人的にヨーヨーが一番好きです。

さて、ダックタイピングとは何ですか?OceanAnimalヨーヨーが であり であることがわかっていLandAnimalます。ただし、そうするyoyo instanceof OceanAnimalyoyo instanceof LandAnimal、そうすると、 が返されますfalse。何?

  • あなた:愚かな JavaScript。ATurtleは でOceanAnimalありLandAnimal!
  • JavaScript:私が立っている場所からではありません。私が知っているのは、それが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;
}

同様に、 a の場合LandAnimal:

function isLandAnimal(object) {
    if (typeof object !== "object") return false;
    if (typeof object.walk !== "function") return false;
    return true;
}

これで、 , の代わりに ; の代わりに使用できるようisOceanAnimal(yoyo)yoyo instanceof OceanAnimalなりisLandAnimal(yoyo)ましたyoyo instanceof LandAnimal。これらの両方の機能がtrue、私たちの最愛のヨーヨーに戻ってきます。わーい!

これは、JavaScript でのダック タイピングの簡単な例です。結論として:

アヒルのように歩き、アヒルのように泳ぎ、アヒルのように鳴く鳥を見るとき、私はその鳥をアヒルと呼びます。

同様に:

海の動物のように泳ぐ動物を見るとき、私はその動物を海の動物と呼びます。そして、陸上動物のように歩く動物を見るとき、私はその動物を陸上動物と呼びます。

編集:上記のコードの動作をここで確認できます: http://jsfiddle.net/aaditmshah/X9M4G/

于 2012-10-07T09:07:02.727 に答える
10

サブクラス化を試みる代わりに、ダックタイピング イニシアチブを使用してコンポーネントと依存性注入を拡張してみませんか?

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 のポイントは、オブジェクトの構築は、オブジェクトを使用するプログラムの関心事ではないということです。

メソッド/プロパティ名が同じである限り、サブ/スーパー/静的継承に関係なく、プログラムはそれらを使用します。

編集: サンプルコンポーネントを追加

ここには、私が結び付けているいくつかの異なるアイデアがあります。したがって、一度に1ドット:

まず、ダックタイピングの基本的な前提:

// 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();です。各アイテムは、それ自体と、必要なことを行う特別な方法について心配することができます。

ダックタイピング

次は依存性注入です。ある意味では、依存性注入もダックタイピングを使用しますが、クラスの外側ではなく内側で (または、私が上で行ったようにそれを行う場合は、内側から開始し、ダックタイピングを許可します)外側も同様)。

次のように考えてみてください。人が何かを継承する代わりに、ただそれを手渡すだけです。

soldier、 a sniperplaneおよび がある場合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 () {};
};

これで、さまざまな種類の銃ができました...関数名が同じで、すべて弾丸を扱うため、サブクラス化を試みることができると思うかもしれません... ...しかし、あなたはそうしませんする必要はありません-これらの銃はどれも、発砲またはリロードするときに同じことをしません... ...そのため、他の言語でメソッド/プロパティに書き込む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メソッドがある場合、ゲームロジックでユニットをダックタイピングすることもできます.

最終的に、コンストラクターはモジュールを保持するだけです。1 つのモジュールは射撃を処理し、1 つのモジュールは地面の動きを処理し、1 つを描画用に、もう 1 つをプレイヤー コントロールに、などなど...コンストラクターは、ピースを接触させる以外に何もする必要はありません。さまざまな種類の銃、さまざまな種類の動き、さまざまな種類のヘルスを与えることで、ユニットを特別なものにすることができます。これらは内部で異なる動作をしますが、パブリック アクセスのプロパティとメソッド名は同じです。

于 2012-10-06T19:26:13.280 に答える
1

したがって、次の 2 つの機能があります。

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 ];
    });        
}

使用法:

var simba = make({
    name: "Simba",
    type: "lion"
});

addMethods( simba, {
    swim: function () {},
    run: function () {}
});

addMethods( simba, {
    hunt: function () {},
    kill: function () {}
});

ライブデモ: http://jsfiddle.net/UETVc/

于 2012-10-06T18:41:14.363 に答える