Перейти до вмісту

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

Матеріал з Вікіпедії — вільної енциклопедії.

Прототипне програмування — стиль об'єктно-орієнтованого програмування, при якому відсутнє поняття класу, а повторне використання (успадкування) проводиться шляхом клонування наявного примірника об'єкта — прототипу.

Канонічним прикладом прототип-орієнтованої мови є мова Self. Надалі цей стиль програмування почав набувати популярності й був покладений в основу таких мов програмування, як JavaScript, Cecil, NewtonScript, Io, Slate, MOO, REBOL, Kevo та інших.

Порівняння з клас-орієнтованим підходом

[ред. | ред. код]

У мовах, що базуються на понятті «клас», всі об'єкти розділені на два основних типи — класи та екземпляри. Клас визначає структуру і функціональність (поведінку), однакову для всіх екземплярів даного класу. Екземпляр є носієм даних — тобто володіє станом, що міняється відповідно до поведінки, заданої класом.

Прихильники прототипного програмування часто стверджують, що мови, засновані на класах, призводять до надмірної концентрації на таксономії класів і на відносинах між ними. На противагу цьому, прототипування загострює увагу на поведінці певної (невеликої) кількості «зразків», які потім класифікуються як «базові» об'єкти й використовуються для створення інших об'єктів. Багато прототип-орієнтованих систем підтримують зміну прототипів під час виконання програми, тоді як лише невелика частина клас-орієнтованих систем (наприклад, Smalltalk) дозволяють динамічно змінювати класи.

Хоча переважна більшість прототип-орієнтованих систем це інтерпретовані мови з динамічною типізацією, технічно можливо додати прототипування і в мови зі статичною перевіркою типів. Мова Omega є одним із прикладів такої системи.

Конструювання об'єктів

[ред. | ред. код]

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

У прототип-орієнтованих системах надається два методи створення нового об'єкта: клонування наявного об'єкта, або створення об'єкта «з нуля». Для створення об'єкта з нуля програмісту надаються синтаксичні засоби додавання властивостей і методів в об'єкт. Надалі, з отриманого об'єкта може бути отримана повна копія, клон. У процесі клонування копія успадковує всі характеристики свого прототипу, але з цього моменту вона стає самостійною і може бути змінена. У деяких реалізаціях копії зберігають посилання на об'єкти-прототипи, делегуючи їм частину своєї функціональності, при цьому зміна прототипу може торкнутися всіх його копій. В інших реалізаціях нові об'єкти повністю незалежні від своїх прототипів. Розглянемо кожну з цих реалізацій.

Делегування

[ред. | ред. код]

У прототип-орієнтованих мовах, що використовують делегування, середовище виконання здатне виконувати диспетчеризацію викликів методів (або пошук потрібних даних) просто подорожуючи ланцюжком делегування вказівників (від об'єкта до його прототипу), до збігу. На відміну від відношення «клас — примірник», відношення «прототип — нащадки» не вимагає, щоб об'єкти-нащадки зберігали структурну подібність зі своїм прототипом. З часом вони можуть адаптуватися і поліпшуватися, але при цьому немає потреби переробляти прототип. Важливо, що додавати/видаляти/модифікувати можна не тільки дані, а й функції, при цьому функції теж виявляються об'єктами першого рівня. Внаслідок цього більшість прототип-орієнтованих мов називають дані та методи об'єкта «слотами» (комірками).

Каскадування

[ред. | ред. код]

При «чистому» прототипуванні — його називають також каскадним та подання в Kevo — клоновані об'єкти не зберігають посилань на свої прототипи. Прототип копіюється один в один, зі всіма методами та атрибутами, і копії присвоюється нове ім'я (посилання). Це нагадує мітоз біологічних клітин.

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

До числа недоліків можна включити труднощі з поширенням змін у системі: модифікація прототипу не тягне за собою негайну й автоматичну зміну всіх його нащадків. Тим не менше, Kevo надає додаткові засоби для публікації змін серед множини об'єктів, причому на підставі їхньої подібності («сімейної схожості»), а не за наявністю загального предка, що типово для моделей з делегуванням.

Інший недолік в тому, що найпростіші реалізації цієї моделі призводять до збільшеної (в порівнянні з моделлю делегування) витрати пам'яті, тому що кожен клон, поки він не змінений, буде містити копію даних свого прототипу. Однак ця проблема розв'язана оптимальним поділом незмінених даних і застосуванням «ледачого копіювання» — що й було використано в Kevo.

Приклад

[ред. | ред. код]
// Приклад прототипного стилю успадкування в JavaScript

// «з нуля» об'єкт створюється із застосуванням буквеної нотації JSON {}.
const foo = {one: 1, two: 2};
// інший об'єкт «з нуля»
const bar = {three: 3};

// Деякі рушії JavaScript, такі як Gecko і Webkit, можуть прямо маніпулювати прототипними зв'язками.
// Для простоти, припустимо, що наступний рядок працює незалежно від використаного рушія:
bar.__proto__ = foo; // bar наразі є нащадком foo.

// тепер ми можемо із bar доступитися до властивостей foo
bar.one // це дає 1.

// об'єкт-нащадок, звісно, зберігає і свої властивості
bar.three // тут лишається 3.

Критика

[ред. | ред. код]

Прихильників клас-орієнтованих об'єктних моделей, що критикують прототипний підхід, часто турбують ті самі проблеми, якими стурбовані прихильники статичної типізації у відношенні до мов з динамічною типізацією. Зокрема, обговорення обертаються навколо таких тем, як правильність, безпека, передбачуваність та ефективність програми.

Що стосується перших трьох пунктів, то класи часто розглядаються як типи (і справді, в більшості об'єктноорієнтованих мов зі статичною типізацією так воно і є), і передбачається, що класи надають певні домовленості й гарантують, що екземпляри будуть поводитися цілком певним чином.

У частині ефективності, оголошення класів значно спрощує компілятору завдання оптимізації, роблячи ефективнішими як методи, так і пошук атрибутів у примірниках. У випадку мови Self чимала частина часу була витрачена на розробку таких технік компіляції та інтерпретації, які дозволили б наблизити продуктивність прототип-орієнтованих систем до їхніх клас-орієнтованих конкурентів.

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

Джерела

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