Программирование на основе прототипов - Prototype-based programming

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

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

Первым прототипно-ориентированным языком программирования был Self , разработанный Дэвидом Ангаром и Рэндаллом Смитом в середине 1980-х годов для исследования тем в области объектно-ориентированного проектирования языков. С конца 1990-х годов бесклассовая парадигма становится все более популярной. Некоторые современные прототипы-ориентированных языков JavaScript (и другие ECMAScript реализации , такие как JScript и флэш «s ActionScript 1.0), Lua , Cecil , NewtonScript , Ио , Ioke , МОО , REBOL и АХК .

Дизайн и реализация

Прототипное наследование в JavaScript описывается Дугласом Крокфордом как:

Вы создаете объекты-прототипы, а затем ... создаете новые экземпляры. Объекты изменяемы в JavaScript, поэтому мы можем дополнять новые экземпляры, давая им новые поля и методы. Затем они могут выступать в качестве прототипов даже для более новых объектов. Нам не нужны классы, чтобы создавать множество похожих объектов… Объекты наследуются от объектов. Что может быть более объектно-ориентированным, чем это?

Сторонники программирования на основе прототипов утверждают, что оно побуждает программиста сосредоточиться на поведении некоторого набора примеров и лишь позднее беспокоиться о классификации этих объектов на архетипические объекты, которые позже используются аналогично классам . Многие системы на основе прототипов поощряют изменение прототипов во время выполнения , в то время как только очень немногие объектно-ориентированные системы на основе классов (например, динамическая объектно-ориентированная система, Common Lisp , Dylan , Objective-C , Perl , Python , Ruby) , или Smalltalk ) позволяют изменять классы во время выполнения программы.

Почти все системы на основе прототипов основаны на интерпретируемых и динамически типизированных языках. Однако системы, основанные на статически типизированных языках, технически осуществимы. Язык Omega, обсуждаемый в Prototype-Based Programming, является примером такой системы, хотя, согласно веб-сайту Omega, даже Omega не является исключительно статическим, а его компилятор может использовать статическое связывание там, где это возможно, и может повысить эффективность программа."

Строительство объекта

В языках, основанных на прототипах, нет явных классов. Объекты наследуются напрямую от других объектов через свойство прототипа. Свойство prototype вызывается prototypeв Self и JavaScript или protoв Io . Есть два метода создания новых объектов: создание объекта ex nihilo («из ничего») или путем клонирования существующего объекта. Первый поддерживается посредством некоторой формы литерала объекта , объявлений, в которых объекты могут быть определены во время выполнения с помощью специального синтаксиса, такого как {...}и переданы непосредственно в переменную. Хотя большинство систем поддерживают различные варианты клонирования, создание объекта ex nihilo не так заметно.

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

Системы, поддерживающие создание объектов ex nihilo, позволяют создавать новые объекты с нуля без клонирования существующего прототипа. Такие системы предоставляют специальный синтаксис для определения свойств и поведения новых объектов без ссылки на существующие объекты. Во многих языках прототипов существует корневой объект, часто называемый Object , который устанавливается в качестве прототипа по умолчанию для всех других объектов, созданных во время выполнения, и который несет обычно необходимые методы, такие как toString()функция для возврата описания объекта в виде строки. . Один полезного аспект на пустом месте создания объекта, чтобы гарантировать , что слот нового объекта (свойства и методы) имена не имеют пространство имен конфликтов с верхним уровнем объекта объектом. (В JavaScript языке, можно сделать это с помощью нуль - прототипа, то есть Object.create(null).)

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

// Example of true prototypal inheritance style 
// in JavaScript.

// object creation using the literal 
// object notation {}.
const foo = { name: "foo", one: 1, two: 2 };

// Another object.
const bar = { two: "two", three: 3 };

// Object.setPrototypeOf() is a method introduced in ECMAScript 2015.
// For the sake of simplicity, let us pretend 
// that the following line works regardless of the 
// engine used:
Object.setPrototypeOf(bar, foo); // foo is now the prototype of bar.

// If we try to access foo's properties from bar 
// from now on, we'll succeed. 
bar.one; // Resolves to 1.

// The child object's properties are also accessible.
bar.three; // Resolves to 3.

// Own properties shadow prototype properties
bar.two; // Resolves to "two"
bar.name; // unaffected, resolves to "foo"
foo.name; // Resolves to "foo"

Другой пример:

const foo = { one: 1, two: 2 };

// bar.[[prototype]] = foo
const bar = Object.create(foo);

bar.three = 3;

bar.one; // 1
bar.two; // 2
bar.three; // 3

Делегация

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

Конкатенация

В конкатенативном прототипировании - подходе, реализованном в языке программирования Kevo - нет видимых указателей или ссылок на исходный прототип, из которого клонируется объект. Объект-прототип (родительский) копируется, а не связывается, и делегирования нет. В результате изменения прототипа не будут отражены в клонированных объектах.

Основное концептуальное отличие этой схемы состоит в том, что изменения, внесенные в объект-прототип, не распространяются автоматически на клоны. Это можно рассматривать как преимущество или недостаток. (Тем не менее, Kevo предоставляет дополнительные примитивы для публикации изменений в наборах объектов на основе их сходства - так называемого семейного сходства или механизма семейства клонов - а не через таксономическое происхождение, как это типично в модели делегирования.) Это также иногда утверждается. это прототипирование на основе делегирования имеет дополнительный недостаток, заключающийся в том, что изменения дочернего объекта могут повлиять на последующие операции родительского объекта. Однако эта проблема не присуща модели, основанной на делегировании, и не существует в языках, основанных на делегировании, таких как JavaScript, которые гарантируют, что изменения дочернего объекта всегда записываются в самом дочернем объекте, а не в родительских (т.е. value затеняет родительское значение, а не изменяет родительское значение).

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

Критика

Сторонники объектных моделей на основе классов, которые критикуют системы на основе прототипов, часто имеют проблемы, аналогичные опасениям, которые сторонники систем статических типов для языков программирования имеют в отношении систем динамических типов (см . Тип данных ). Обычно такие проблемы включают: правильность , безопасность , предсказуемость , эффективность и незнание программиста.

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

Что касается эффективности, объявление классов упрощает многие оптимизации компилятора, которые позволяют разработать эффективный метод и поиск переменных экземпляра. Для языка Self много времени было потрачено на разработку, компиляцию и интерпретацию методов для повышения производительности систем на основе прототипов по сравнению с системами на основе классов.

Общая критика языков, основанных на прототипах, заключается в том, что сообщество разработчиков программного обеспечения не знакомо с ними, несмотря на популярность и проникновение на рынок JavaScript . Этот уровень знаний систем, основанных на прототипах, кажется, растет с распространением фреймворков JavaScript и сложным использованием JavaScript по мере развития Интернета . ECMAScript 6 представил классы как синтаксический сахар по сравнению с существующим наследованием на основе прототипов JavaScript, предоставив альтернативный способ создания объектов и работы с наследованием.

Языки, поддерживающие программирование на основе прототипов

Смотрите также

использованная литература

дальнейшее чтение