HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Решаемая задача: реализовать диспетчеризацию по типу своими руками.</p>
1 <p>Решаемая задача: реализовать диспетчеризацию по типу своими руками.</p>
2 <p>Разложим весь процесс на примере библиотеки для работы с геометрическими фигурами. Предположим, что мы можем создавать разные фигуры, такие как треугольник, круг или квадрат. Кроме специфических свойств, у фигур есть и общие, например, периметр или площадь. А так как мы, гипотетически, хотим работать с фигурами единообразно, то реализуем диспетчеризацию по типу на примере функции, вычисляющей общую площадь фигур, размещенных на воображаемом холсте (так обычно называется область, на которой происходит рисование в графических редакторах)</p>
2 <p>Разложим весь процесс на примере библиотеки для работы с геометрическими фигурами. Предположим, что мы можем создавать разные фигуры, такие как треугольник, круг или квадрат. Кроме специфических свойств, у фигур есть и общие, например, периметр или площадь. А так как мы, гипотетически, хотим работать с фигурами единообразно, то реализуем диспетчеризацию по типу на примере функции, вычисляющей общую площадь фигур, размещенных на воображаемом холсте (так обычно называется область, на которой происходит рисование в графических редакторах)</p>
3 <p>При отсутствии готовой диспетчеризации нам придется делать ее руками в том месте, где требуется обобщенное поведение:</p>
3 <p>При отсутствии готовой диспетчеризации нам придется делать ее руками в том месте, где требуется обобщенное поведение:</p>
4 <p>С наличием автоматического механизма диспетчеризации (не важно реализован он в самом языке или нами самостоятельно) код сокращается до следующего:</p>
4 <p>С наличием автоматического механизма диспетчеризации (не важно реализован он в самом языке или нами самостоятельно) код сокращается до следующего:</p>
5 <p>В примере выше функция getArea сама по себе не занимается вычислением площади. Это вычисление реализовано для каждой фигуры совершенно независимо. Все, что делает getArea, это перенаправляет запрос на расчет площади в соответствующую функцию.</p>
5 <p>В примере выше функция getArea сама по себе не занимается вычислением площади. Это вычисление реализовано для каждой фигуры совершенно независимо. Все, что делает getArea, это перенаправляет запрос на расчет площади в соответствующую функцию.</p>
6 <p>Алгоритм диспетчеризации в примере выше следующий:</p>
6 <p>Алгоритм диспетчеризации в примере выше следующий:</p>
7 <ol><li>getArea извлекает тип (его название) из фигуры.</li>
7 <ol><li>getArea извлекает тип (его название) из фигуры.</li>
8 <li>getArea обращается к глобальному хранилищу (виртуальная таблица) для поиска нужной реализации настоящей функции вычисления площади.</li>
8 <li>getArea обращается к глобальному хранилищу (виртуальная таблица) для поиска нужной реализации настоящей функции вычисления площади.</li>
9 <li>Если реализация найдена, то getArea ее вызывает с нужными аргументами и возвращает результат наружу.</li>
9 <li>Если реализация найдена, то getArea ее вызывает с нужными аргументами и возвращает результат наружу.</li>
10 </ol><p>Важное следствие этого алгоритма в том, что для работы автоматической диспетчеризации необходимо, чтобы реальные функции getArea были занесены в виртуальную таблицу, иначе до них невозможно будет достучаться.</p>
10 </ol><p>Важное следствие этого алгоритма в том, что для работы автоматической диспетчеризации необходимо, чтобы реальные функции getArea были занесены в виртуальную таблицу, иначе до них невозможно будет достучаться.</p>
11 <h2>Виртуальная таблица</h2>
11 <h2>Виртуальная таблица</h2>
12 <p>Выполняет две задачи, которые мы рассмотрим ниже.</p>
12 <p>Выполняет две задачи, которые мы рассмотрим ниже.</p>
13 <h3>Регистрация</h3>
13 <h3>Регистрация</h3>
14 <p>Первая задача - это регистрация функций тех типов, по которым мы планируем делать диспетчеризацию:</p>
14 <p>Первая задача - это регистрация функций тех типов, по которым мы планируем делать диспетчеризацию:</p>
15 <p>Тогда модуль, реализующий наш тип, будет выглядеть так:</p>
15 <p>Тогда модуль, реализующий наш тип, будет выглядеть так:</p>
16 <p>Как видно из примера выше, по большей части Circle является типичной абстракцией, за исключением пары моментов:</p>
16 <p>Как видно из примера выше, по большей части Circle является типичной абстракцией, за исключением пары моментов:</p>
17 <ol><li>Внутри создается привязка к типу. Соответственно все селекторы должны сначала извлечь данные и потом уже работать.</li>
17 <ol><li>Внутри создается привязка к типу. Соответственно все селекторы должны сначала извлечь данные и потом уже работать.</li>
18 <li>С помощью definer происходит регистрация нужных (радиус специфичен для круга, по нему диспетчеризация не нужна) функций в нашей виртуальной таблице.</li>
18 <li>С помощью definer происходит регистрация нужных (радиус специфичен для круга, по нему диспетчеризация не нужна) функций в нашей виртуальной таблице.</li>
19 </ol><p>Наш модуль generic ничего не знает про Circle, да и вообще ничего не знает про тех, кто его использует. В общем случае, для регистрации функции ему нужно знать три значения: имя типа, имя функции и само тело функции, или, другими словами, мы имеем такой интерфейс: register('TypeName', 'funcName', funcBody). А код регистрации выглядел бы так:</p>
19 </ol><p>Наш модуль generic ничего не знает про Circle, да и вообще ничего не знает про тех, кто его использует. В общем случае, для регистрации функции ему нужно знать три значения: имя типа, имя функции и само тело функции, или, другими словами, мы имеем такой интерфейс: register('TypeName', 'funcName', funcBody). А код регистрации выглядел бы так:</p>
20 <p>Обратите внимание на то, что мы находимся внутри модуля Circle и нам приходится в каждом вызове register передавать его название. Это единственная причина, по которой существует функция defmethod. То есть мы сначала специфицируем имя типа для которого будем заполнять функции, а потом делаем это без повторений.</p>
20 <p>Обратите внимание на то, что мы находимся внутри модуля Circle и нам приходится в каждом вызове register передавать его название. Это единственная причина, по которой существует функция defmethod. То есть мы сначала специфицируем имя типа для которого будем заполнять функции, а потом делаем это без повторений.</p>
21 <p>С точки зрения теории мы использовали так называемое<a>частичное применение функции</a>:</p>
21 <p>С точки зрения теории мы использовали так называемое<a>частичное применение функции</a>:</p>
22 <p>Что эквивалентно:</p>
22 <p>Что эквивалентно:</p>
23 <p>Ну и самое главное, а где же происходит регистрация? Куда записываются все эти данные о типах? Ответ достаточно простой. Фактически в наш прекрасный чистый код мы вводим внешнее изменяемое состояние и заполняем его функцией с побочными эффектами (definer). Если открыть модуль generic, то можно увидеть:</p>
23 <p>Ну и самое главное, а где же происходит регистрация? Куда записываются все эти данные о типах? Ответ достаточно простой. Фактически в наш прекрасный чистый код мы вводим внешнее изменяемое состояние и заполняем его функцией с побочными эффектами (definer). Если открыть модуль generic, то можно увидеть:</p>
24 <p>В свою очередь, все функции, которым нужен доступ к таблице, получают его посредством замыкания. Причем только definer изменяет ее, а все остальные - читают.</p>
24 <p>В свою очередь, все функции, которым нужен доступ к таблице, получают его посредством замыкания. Причем только definer изменяет ее, а все остальные - читают.</p>
25 <p>Получается, что methods наполняется в тот момент, когда загружаются типы (выполняется import), использующие модуль generic для регистрации своих функций. Например:</p>
25 <p>Получается, что methods наполняется в тот момент, когда загружаются типы (выполняется import), использующие модуль generic для регистрации своих функций. Например:</p>
26 <h3>Поиск</h3>
26 <h3>Поиск</h3>
27 <p>Вторая задача это, собственно, поиск этих функций:</p>
27 <p>Вторая задача это, собственно, поиск этих функций:</p>
28 <p>Для поиска подходящей функции достаточно знать два параметра: имя типа и имя функции. Если функция найдена, то getMethod возвращает ее вызывающему коду, который, в свою очередь, уже делает вызов найденной функции.</p>
28 <p>Для поиска подходящей функции достаточно знать два параметра: имя типа и имя функции. Если функция найдена, то getMethod возвращает ее вызывающему коду, который, в свою очередь, уже делает вызов найденной функции.</p>
29  
29