Прототипная объектная ориентация
Предыстория
Приветствую. Я уже не раз говорил, что язык JS я считаю если не самым любимым, то как минимум вторым (после C++). Это связано с тем, что благодаря этому, казалось бы, сложному языку, я постиг один из "дзенов" программирования, а именно великую парадигму прототипной ориентации объектно-ориентированного языка. Многие программисты, не знакомые с этим, не побоюсь этого слова, чистой реализацией, считают ее более сложной и даже не объектной, но на самом деле это не так, скорее наоборот, классическая объектная ориентация оказывается всего лишь надстройкой "сахара" над этой парадигмой.
Не так давно, я начал изучать язык скриптов известного редактора Vim, и сразу же обратил внимание на его объектную парадигму, и занесло...
Объекты в Vim
Забегая вперед скажу, что в этой статье речь пойдет о реализации прототипной ориентации в Vim(!), но все попорядку.
В Vim объекты создаются и используются следующим образом (" в Vim это начало строки комментария):
let obj = {'name': 'Artur'} " Создаем объект
function! obj.getName() dict " Создаем метод объекта
return self.name
endfunction
echo obj.getName() " Вызываем метод объекта
Что мы видим из стандартного синтаксиса Vim?
* Классы не используются вообще, то есть нет возможности заранее определить структуру будущего объекта, его свойства и методы, объекты создаются сразу перечислением всех свойств и методов, а затем к ним добавляются методы.
* Нет возможности наследовать поведение, то есть мы не можем создать объект основываясь на структуре другого объекта, нужно явно копировать в новый объект все свойства и методы родителя.
* Снижается повторное использование кода, за счет отсутствия наследования, то есть возможности выносить логику в родительские объекты.
Этот список можно продолжать еще долго, но это есть то, с чего начинается любой объектно-ориентированный язык программирования - ассоциативный массив с возможностью передачи функций в качестве его элементов.
Как из всего этого сделать прототипную модель? Достаточно реализовать всего один метод!
Прототипирование
Сначала немного теории. Чтобы существующая в Vim объектная модель могла хоть немного походить на прототипную, достаточно реализовать возможность наследования реализации. В прототипной модели делается это довольно просто, но чтобы понять это, необходимо забыть о классической реализации объектности в языках программирования. Дело в том, что в прототипной модели нет понятия класса вообще, то есть за класс выступает обычный объект, называемый прототипом. Предположим нам нужно реализовать программу для магазина. Имеем два типа объектов: покупатели и сотрудники. И тот и другой тип объектов по сути является типом "Человек", то есть они отличаются только несколькими дополнительными свойствами и методами, а общие можно вынести в отдельный тип объектов. В качестве типа объектов "Человек" выступает объект, в качестве свойств которого задаются значения по умолчанию. В этот же объект выносятся и все общие методы.
let People = {'name': 0} " Прототип Человек
function! People.getName() dict
return self.name
endFunction
Такой объект называется прототипом, так как на основании его будут создаваться другие типы объектов, а делается это путем создания ссылки на все методы прототипа из дочернего объекта (чтобы не копировать функции), а так же созданием ссылки на сам прототип (чтобы не копировать свойства):
" Функция создает объект на основе прототипа.
" 4 prototype - прототип.
" 4 properties - свойства создаваемого объекта.
function! Expand(prototype, properties)
let obj = {}
" Наследование свойств.
" Наследование реализуется путем формирования ссылки на родительский объект в свойстве parent.
let obj.parent = a:prototype
" Наследование методов.
" Наследование реализуется путем формирования ссылок на методы родительского класса в одноименных методах дочернего.
for k in keys(a:prototype) " Проход по всем методам прототипа
let t = type(a:prototype[k])
if t == 2
let obj[k] = a:prototype[k] " Создание ссылок на методы прототипа из дочернего объекта
endif
endfor
" Формирование свойств.
" Реализуется путем копирования переданного списка в создаваемый объект.
for [k, v] in items(a:properties) " Формирование частных свойств объекта
let t = type(v)
if t == 3 || t == 4
let obj[k] = deepcopy(v)
else
let obj[k] = v
endif
endfor
return obj
endfunction
Вот собственно и вся реализация прототипной модели в Vim. С помощью данной функции можно создавать объекты наследуя структуру других объектов (прототипов). Как это использовать:
let s:Object = {} " Корневой прототип
function s:Object.getType() dict " Метод прототипа
return 'prototype'
endfunction
" Создание объекта через прототип
let s:o1 = Expand(s:Object, {'name': 'Artur'})
function s:o1.getName() dict " Метод объекта
return self.name
endfunction
echo s:o1.getType() " prototype - вызов метода прототипа
echo s:o1.getName() " Artur - вызов метода объекта
Реализация
Как вы могли заметить, прототипная модель это всего лишь механизм, позволяющий создавать ссылки между прототипом и объектом так, чтобы вторым было удобно пользоваться. В моем случае, в объекте создаются ссылки на методы прототипа, это позволяет вызывать их прямо из объекта, при этом не забивая память копиями методов. Так же создается свойство parent в объекте, ссылающееся на прототип, это позволяет обращаться к свойствам родителя без их копирования в объект, а так же переопределять методы родителя:
let s:o1 = Expand(s:Object, {'name': 'Artur'})
function s:o1.getType() dict " Переопределение метода
return 'object'
endfunction
echo s:o1.getType() " object - вызов переопределенного метода
При переопределении метода всегда можно получить доступ к перегружаемому методы через ссылку parent:
echo s:o1.parent.getType() " prototype - вызов родительского метода
Итоги
Никогда не знаешь, сколько интересного можно найти в "стареньком" редакторе. Обязательно изучайте различные реализации давно известных вам решений, это позволяет взглянуть на одни и те же вещи под совершенно разными углами.
URL:
https://visavi.net/articles/528