Пример Javascript Duck Typing?

Некоторые программисты советуют не использовать псевдоклассическое наследование в Javascript, но советуют использовать утиную печать и предоставляют каждому объекту набор возможностей.

Есть ли хороший пример того, как это делается? У меня есть пример ниже, но он назначает только функцию по одному. Можно ли назначить целую группу методов объекту, например, установить прототип OceanAnimal, который может "плавать", "погружаться" и "подниматься", прототип LandAnimal для "run", walk "и" jump ", и пусть объект наследуется от одного или обоих из них? (так что объект рыбы может наследовать или получать возможности 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

Ответ 1

Функции в JavaScript универсальны. Они могут использоваться как подпрограммы, методы, конструкторы, пространства имен, модули и многое другое.

Причина, по которой люди советуют использовать псевдоклассическое наследование в JavaScript, заключается в том, что она скрывает истинную силу JavaScript. Функции такие же выразительные, если не более выразительные, чем объекты. Это доказано церковью Алонсо, которые работают, Lambda Calculus, Тьюринга завершена.

Чтобы ответить на ваш вопрос напрямую, я буду использовать функции для создания 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.";
    };
}

Далее мы создадим конструктор для LandAnimal:

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

Хорошо. Итак, теперь создадим конструктор для Turtle:

Turtle.prototype.type = "turtle";

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

Что здесь происходит? Хорошо, мы хотим, чтобы 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);
}

Так как 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));

Ха-ха. Это было весело. Лично я люблю больше всего yoyo.

Хорошо, так что утка набирает? Мы знаем, что yoyo - это OceanAnimal и a LandAnimal. Однако, если мы делаем yoyo instanceof OceanAnimal или yoyo instanceof LandAnimal, тогда он возвращает false. Что?

  • Вы: Глупый JavaScript. A Turtle - это OceanAnimal и a 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 для нашего любимого yoyo. Ура!

Это простой пример утиного ввода текста в JavaScript. В заключение:

Когда я вижу птицу, которая ходит как утка и плавает, как утка и шарлатанья, как утка, я называю эту птицу утки.

Аналогично:

Когда я вижу животное, которое плавает как океанское животное, я называю это животное океанским животным; и когда я вижу животное, которое ходит как наземное животное, я называю это животное наземным животным.

Изменить: Вы можете увидеть приведенный выше код в действии: http://jsfiddle.net/aaditmshah/X9M4G/

Ответ 2

Вместо того, чтобы пытаться подкласса, почему бы не использовать инициативу утиной печати для расширения с помощью компонентов и инъекции зависимостей?

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.

Они даже не требуют конструктора в первую очередь.
A factory будет работать так же хорошо.

Точка DuckTyping заключается в том, что объект-конструкция не является проблемой программы, использующей объект.

Пока у него есть те же имена метода/свойства, программа будет использовать их независимо от суб/супер/статического наследования.

EDIT: добавлены примеры компонентов

Здесь есть пара различных идей, которые я связываю. Таким образом, одна точка за раз:

Во-первых, основная предпосылка утиного ввода:

// 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();. Каждый элемент может беспокоиться о себе, и особым образом он делает то, что ему нужно делать.

Утка-Typing

Далее зависимость-инъекция. В некотором смысле, зависимость-инъекция также использует утиную печать, но на внутри вашего класса вместо внешней (или если вы делаете это, как я сделал вверх, она начинается внутри, а затем позволяет также печатать на утке снаружи).

Подумайте об этом так: Вместо того, чтобы наследовать кого-то, просто передайте его им.

Если у вас есть soldier, a sniper a plane и a 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, то вы можете утаивать свои юниты в игровой логике как хорошо.

В конечном счете, конструктор будет просто содержать модули: один модуль для обработки стрельбы, один для управления движением земли, один для рисования, один для управления плеером и т.д.... Конструктору не нужно было ничего делать, кроме того, что они касались друг друга, и вы могли бы создавать специальные предметы, предоставляя им различные виды оружия или различные виды движения или различные виды здоровья, которые действуют по-разному на внутри, но имеют те же свойства и имена методов для открытого доступа.

Ответ 3

Итак, у вас есть две функции:

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/