Введение

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

Обзор

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

Случаи использования

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

Распространенная идиома

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

Двойная диспетчерская система на C#

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

Двойная диспетчерская в Эйфеле

Язык программирования Эйфель позволяет применить концепцию агентов для решения проблемы двойной отправки. Приведенный ниже пример демонстрирует использование конструкции языка агентов для решения этой проблемы. Рассмотрим предметную область, включающую различные формы ФИГУР и ПОВЕРХНОСТЕЙ для рисования, на которых можно рисовать ФИГУРЫ. И ФИГУРА, и ПОВЕРХНОСТЬ знают о функции `draw` внутри себя, но не знают о ней в другом классе. Наша задача – обеспечить ковариантное взаимодействие объектов этих двух типов посредством двойной отправки, используя шаблон "Посетитель". Суть проблемы заключается в том, чтобы обеспечить полиморфную ПОВЕРХНОСТЬ возможность нарисовать полиморфную ФИГУРУ на себе.

Настройка

Прежде чем рассматривать SHAPE или SURFACE, нам необходимо проанализировать высокоуровневое независимое использование нашей двойной диспетчеризации.

Пример Эйфеля

Что касается двойной диспетчеризации, Эйфель позволяет разработчику и программисту еще больше снизить уровень прямого знания об объектах, отделив классовые процедуры от самих классов, превратив их в агенты и передавая эти агенты вместо непосредственных вызовов функций объекта. Агенты также имеют определенные сигнатуры и возможные результаты (в случае запросов), что делает их идеальным инструментом для статической проверки типов без раскрытия конкретных деталей объекта. Агенты полностью полиморфны, поэтому результирующий код содержит только те специфические знания, которые необходимы для выполнения его локальной задачи. В противном случае, не возникает дополнительной нагрузки на сопровождение, связанной с распространением знаний о специфических внутренних классах среди множества ковариантных объектов. Использование и механизм агентов это обеспечивают. Возможным недостатком использования агентов является то, что они вычислительно дороже, чем прямой вызов. Учитывая это, не следует автоматически использовать агенты в двойной диспетчеризации и при применении шаблона "посетитель". Если четко определены границы дизайна в отношении домена типов классов, участвующих в ковариантных взаимодействиях, то прямой вызов будет более эффективным решением с точки зрения вычислительных затрат. Однако, если ожидается расширение или существенное изменение домена классов участвующих типов, то агенты представляют собой отличное решение для снижения нагрузки на сопровождение в шаблоне двойной диспетчеризации.