Прототипная программирования

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

Каноническим примером прототип-ориентированного языка является язык Self. В дальнейшем этот стиль программирования начал приобретать популярность и был положен в основу таких языков программирования, как JavaScript, Cecil, NewtonScript, Io, Slate, MOO, REBOL, Kevo и других.


1. Сравнение с класс-ориентированным подходом

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

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

Хотя подавляющее большинство прототип-ориентированных систем это интерпретированы языка с динамической типизацией, технически возможно добавить прототипирования и в языки со статической проверкой типов. Язык Omega является одним из примеров такой системы.


2. Конструирование объектов

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

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


2.1. Делегирование

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


2.2. Каскадирование

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

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

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

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


3. Пример

 / / Пример прототипеого стиля наследования в JavaScript  / / "С нуля" объект создается с применением буквенной нотации JSON {}.  var  foo  =  {  one  :  1  ,  two  :  2  }  ;  / / Другой объект "с нуля"  var  bar  =  {  three  :  3  }  ;  / / Движители Gecko и Webkit JavaScript могут прямо манипулировать прототипная связями  / / Для простоты, предположим, что следующая строка работает независимо от использованного двигателя:  bar.  __proto__  =  foo  ;  / / Bar сейчас является потомком foo.  / / Теперь мы можем с bar подступиться к свойствам foo  bar.  one  / / Это дает 1.  / / Объект-потомок конечно сохраняет и свои свойства  bar.  three  / / Здесь остается 3. 

4. Критика

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

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

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

Наконец, возможно самым общим местом критики против прототипного программирования является то, что сообщество разработчиков ПО недостаточно хорошо знакомый с ним, несмотря на популярность и распространенность JavaScript. К тому же, так как прототип-ориентированные системы являются сравнительно новыми и все еще немногочисленными и редкими, приемы разработки с их использованием до сих пор не получили широкого распространения.


5. Языки

Литература

  • Иан Грэхем Объектно-ориентированные методы. Принципы и практика = Object-Oriented Methods: Principles & Practice. - 3-е изд. - Москва: "Вильямс", 2004. - С. 880. - ISBN 0-201-61913-X