Ориентация объектов на основе прототипа. Хороший, плохой, злой?

Я из классов языков ориентации объектов, и недавно я изучал эти фантастические динамические языки (JavaScript, Python и Lua), и мне нужны некоторые советы о том, как использовать OO на этих языках. Было бы полезно знать подводные камни и недостатки такого подхода и преимущества по сравнению с традиционными ОО.

Общее представление, которое я получил, это то, что основанный на прототипе OO в основном программирует объекты, но не имеет никакого стандарта о том, как их использовать, тогда как в обычном OO существует фиксированный предопределенный способ создания и использования объектов.

В целом, что такое хорошая, плохая и уродливая часть такого подхода?

Ответ 1

ООП на основе прототипов плохо относится к проверке статического типа, что некоторые могут считать плохой или уродливой. OO на основе прототипа имеет стандартный способ создания новых объектов, вы клонируете и модифицируете существующие объекты. Вы также можете создавать заводы и т.д.

Я думаю, что люди, подобные большинству ( "хорошие" ), в том, что OO с прототипом очень легкий и гибкий, предлагая очень высокое соотношение мощности к весу.

Для советов о том, как использовать OO на основе прототипов, отличным местом для начала является оригинальная Self-бумага на Сила Простота.

Ответ 2

Чтобы сохранить полосу пропускания, вот ссылка на мой ответ на вопрос "Как я могу эмулировать" классы "в JavaScript? (с или без сторонней библиотеки)" . Он содержит дополнительные ссылки, а также примеры.

Короткий ответ: сердце прототипа JavaScript OO - это делегирование. В этом стиле ООП различные объекты одного и того же "класса" могут делегировать обработку методов и свойств одному и тому же прототипу (как правило, некоторого третьего объекта):

var foo = {
    property: 42,
    inc: function(){
        ++this.counter;
    },
    dec: function(){
        --this.counter;
    }
};
// Note: foo does not define `counter`.

Позвольте создать конструктор для объектов с foo в качестве прототипа. Фактически, все необработанные будут делегированы foo.

var Bar = function(){
    this.counter = 0;
};
Bar.prototype = foo;  // This is how we set up the delegation.

// Some people refer to Bar (a constructor function) as "class".

var bar = new Bar();

console.log(bar.counter);  // 0 --- Comes from bar itself
console.log(bar.property); // 42 --- Not defined in bar, comes from foo

bar.inc();  // Not defined in bar => delegated to foo
bar.inc();
bar.dec();  // Not defined in bar => delegated to foo
// Note: foo.inc() and foo.dec() are called but this === bar
// that is why bar is modified, not foo.

console.log(bar.counter);  // 1 --- Comes from bar itself

Определите inc() непосредственно в строке:

bar.inc = function(){
    this.counter = 42;
};

bar.inc();  // Defined in bar => calling it directly.
            // foo.inc() is not even called.
console.log(bar.counter);  // 42 --- Comes from bar

Настройка одиночной цепочки наследования:

var Baz = function(){
    this.counter = 99;
};
Baz.protype = new Bar();

var baz = new Baz();

console.log(baz.counter); // 99
baz.inc();
console.log(baz.counter); // 100

console.log(baz instanceof Baz);    // true
console.log(baz instanceof Bar);    // true
console.log(baz instanceof Object); // true

Аккуратный, eh?

Ответ 4

Классическое наследование по своей сути ошибочно с точки зрения гибкости, поскольку мы говорим: "этот объект имеет этот тип, а другой нет". Некоторые языки вводят множественное наследование для облегчения этого, но множественное наследование имеет свои собственные подводные камни, и поэтому преимущества чистого состава над наследованием (которые на статически типизированном языке являются временем выполнения, а не механизмом времени компиляции) становятся ясными.

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

Таким образом, прототип означает гораздо более гибкую разработку модулей.

Конечно, совсем другое дело сказать, что ЛЕГКО развиваться без статической типизации. ИМО, это не так.

Ответ 5

Хорошо, во-первых, прототипная модель не так уж и различна в реальности; Smalltalk использует аналогичную схему; класс - это объект с методами классов.

Взглянув из класса POV, класс действительно является классом эквивалентности объектов с одинаковыми данными и всеми теми же методами; вы можете посмотреть на добавление метода к прототипу в качестве создания нового подкласса.

Реализация проста, но очень сложно сделать эффективную проверку типов.