Двойная отправка - Double dispatch

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

Дэн Ингаллс впервые описано, как использовать двойную отправку в Болтовня, называя это множественный полиморфизм.[1]

Обзор

Основная проблема заключается в том, как отправить сообщение различным методам в зависимости не только от получателя, но и от аргументов.

С этой целью такие системы, как ЗАКРЫТЬ осуществлять множественная отправка. Двойная отправка - это еще одно решение, которое постепенно снижает полиморфизм в системах, не поддерживающих множественную отправку.

Сценарии использования

Двойная отправка полезна в ситуациях, когда выбор вычисления зависит от типов его аргументов во время выполнения. Например, программист может использовать двойную отправку в следующих ситуациях:

  • Сортировка смешанного набора объектов: алгоритмы требуют, чтобы список объектов был отсортирован в некотором каноническом порядке. Решение о том, стоит ли один элемент перед другим, требует знания обоих типов и, возможно, некоторого подмножества полей.
  • Адаптивные алгоритмы столкновения обычно требуют, чтобы столкновения между разными объектами обрабатывались по-разному. Типичный пример - игровая среда, где столкновение между космическим кораблем и астероидом вычисляется иначе, чем столкновение между космическим кораблем и космической станцией.[2]
  • Алгоритмы рисования которые требуют точек пересечения перекрытия спрайты быть обработанным другим способом.
  • Кадровый менеджмент системы могут отправка разные виды работ для разного персонала. А график Алгоритм, которому дан объект "человек" с типом "бухгалтер" и объект "работа" с типом "инженерное дело", отклоняет планирование этого человека для этой работы.
  • Обработка событий системы, которые используют как тип события, так и тип объекта-приемника для вызова правильной процедуры обработки событий.
  • Замок и ключ системы, в которых существует множество типов замков и много типов ключей, и каждый тип ключа открывает несколько типов замков. Вам необходимо знать не только типы задействованных объектов, но и подмножество «информации о конкретном ключе, имеющей отношение к тому, чтобы увидеть, открывает ли конкретный ключ конкретную блокировку», различается для разных типов блокировки.

Общая идиома

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

Пример на Ruby

Обычный вариант использования - отображение объекта на порте дисплея, которым может быть экран, принтер или что-то еще, чего еще не существует. Это наивная реализация того, как обращаться с этими разными СМИ.

класс Прямоугольник  def display_on(порт)    # выбирает правильный код в зависимости от класса объекта    кейс порт      когда DisplayPort        # код для отображения на DisplayPort      когда PrinterPort        # код для отображения на PrinterPort      когда RemotePort        # код для отображения на RemotePort    конец  конецконец

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

Тогда гораздо более чистым и удобным в обслуживании решением является выполнение второй отправки, на этот раз для выбора правильного метода отображения объекта на носителе:

класс Прямоугольник  def display_on(порт)    # вторая отправка    порт.display_rectangle(я)  конецконецкласс Овал  def display_on(порт)    # вторая отправка    порт.display_oval(я)  конецконецкласс DisplayPort  def display_rectangle(объект)    # код для отображения прямоугольника на DisplayPort  конец  def display_oval(объект)    # код для отображения овала на DisplayPort  конец  # ...конецкласс PrinterPort  def display_rectangle(объект)    # код для отображения прямоугольника на PrinterPort  конец  def display_oval(объект)    # код для отображения овала на PrinterPort  конец  # ...конец

Двойная отправка в C ++

На первый взгляд двойная отправка кажется естественным результатом перегрузка функции. Перегрузка функции позволяет вызываемой функции зависеть от типа аргумента. Однако перегрузка функций выполняется во время компиляции с использованием "искажение имени "где внутреннее имя функции кодирует тип аргумента. Например, функция foo (число) может называться внутри __foo_i и функция foo (двойной) можно назвать __foo_d. Таким образом, нет конфликта имен и поиска в виртуальной таблице. Напротив, динамическая отправка основана на типе вызывающего объекта, то есть использует виртуальные функции (переопределение) вместо перегрузка функции, и приводит к поиску в vtable. Рассмотрим следующий пример, написанный на C ++, столкновений в игре:

класс Космический корабль {};класс АполлонКосмический корабль : общественный Космический корабль {};класс Астероид {общественный:  виртуальный пустота Сталкиваются с(Космический корабль&) {    стандартное::cout << "Астероид врезался в космический корабль п";  }  виртуальный пустота Сталкиваются с(АполлонКосмический корабль&) {    стандартное::cout << "Астероид врезался в космический корабль" Аполлон " п";  }};класс ВзрывающийсяАстероид : общественный Астероид {общественный:  пустота Сталкиваются с(Космический корабль&) отменять {    стандартное::cout << "Взрывающийся астероид попал в космический корабль п";  }  пустота Сталкиваются с(АполлонКосмический корабль&) отменять {    стандартное::cout << "Взрывающийся астероид попал в космический корабль" Аполлон " п";  }};

Если у тебя есть:

Астероид Астероид;Космический корабль космический корабль;АполлонКосмический корабль АполлонКосмический корабль;

тогда из-за перегрузки функции

Астероид.Сталкиваются с(космический корабль); Астероид.Сталкиваются с(АполлонКосмический корабль);

напечатает, соответственно, Астероид врезался в космический корабль и Астероид врезался в космический корабль Аполлона, без использования какой-либо динамической отправки. Более того:

ВзрывающийсяАстероид Взрывающийся астероид;Взрывающийся астероид.Сталкиваются с(космический корабль); Взрывающийся астероид.Сталкиваются с(АполлонКосмический корабль);

напечатает Взрывающийся астероид попал в космический корабль и Взрывающийся астероид попал в космический корабль Аполлона соответственно, опять же без динамической рассылки.

Со ссылкой на Астероид, используется динамическая отправка, и этот код:

Астероид& Ссылка на Астероид = Взрывающийся астероид;Ссылка на Астероид.Сталкиваются с(космический корабль); Ссылка на Астероид.Сталкиваются с(АполлонКосмический корабль);

отпечатки Взрывающийся астероид попал в космический корабль и Взрывающийся астероид попал в космический корабль Аполлона, опять же, как и ожидалось. Однако следующий код не работает должным образом:

Космический корабль& theSpaceShipReference = АполлонКосмический корабль;Астероид.Сталкиваются с(theSpaceShipReference);Ссылка на Астероид.Сталкиваются с(theSpaceShipReference);

Желаемое поведение - связать эти вызовы с функцией, которая принимает АполлонКосмический корабль в качестве аргумента, поскольку это конкретный тип переменной, что означает, что ожидаемый результат будет Астероид врезался в космический корабль Аполлона и Взрывающийся астероид попал в космический корабль Аполлона. Однако на самом деле результат Астероид врезался в космический корабль и Взрывающийся астероид попал в космический корабль. Проблема в том, что, хотя виртуальные функции отправляются динамически в C ++, перегрузка функций выполняется статически.

Описанная выше проблема может быть решена моделирование двойная отправка, например, с помощью шаблон посетителя. Предположим, что существующий код расширен так, что оба Космический корабль и АполлонКосмический корабль даны функции

виртуальный пустота Сталкиваются с(Астероид& inAsteroid) {  inAsteroid.Сталкиваются с(*этот);}

Затем, хотя предыдущий пример по-прежнему работает некорректно, переформатирование вызовов так, чтобы космический корабль был агентом, дает нам желаемое поведение:

Космический корабль& theSpaceShipReference = АполлонКосмический корабль;Астероид& Ссылка на Астероид = Взрывающийся астероид;theSpaceShipReference.Сталкиваются с(Астероид);theSpaceShipReference.Сталкиваются с(Ссылка на Астероид);

Он распечатывает Астероид врезался в космический корабль Аполлона и Взрывающийся астероид попал в космический корабль Аполлона, как и ожидалось. Ключ в том, что theSpaceShipReference.CollideWith (theAsteroidReference); делает следующее во время выполнения:

  1. theSpaceShipReference является ссылкой, поэтому C ++ ищет правильный метод в таблице vtable. В этом случае он вызовет ApolloSpacecraft :: CollideWith (Астероид &).
  2. В пределах ApolloSpacecraft :: CollideWith (Астероид &), inAsteroid это ссылка, поэтому inAsteroid.CollideWith (* это) приведет к другой поиск в vtable. В таком случае, inAsteroid это ссылка на ВзрывающийсяАстероид так ExplodingAsteroid :: CollideWith (ApolloSpacecraft &) будет называться.

Двойная отправка в C #

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

Двойная отправка в Эйфеле

В Эйфель Язык программирования может использовать концепцию агентов для решения проблемы двойной диспетчеризации. В приведенном ниже примере конструкция языка агента применяется к проблеме двойной отправки.

Рассмотрим проблемную область с различными формами ФОРМЫ и рисования ПОВЕРХНОСТИ, на которой нужно рисовать ФОРМУ. И SHAPE, и SURFACE знают о функции, называемой `draw ', сами по себе, но не друг в друге. Мы хотим, чтобы объекты двух типов одновременно взаимодействовали друг с другом при двойной отправке с использованием шаблона посетителя.

Задача состоит в том, чтобы получить полиморфную ПОВЕРХНОСТЬ, чтобы нарисовать на себе полиморфную ФОРМУ.

Вывод

В приведенном ниже примере выходных данных показаны результаты полиморфной передачи двух объектов посетителя SURFACE через список полиморфных объектов SHAPE. Шаблон кода посетителя осведомлен только о ФОРМЕ и ПОВЕРХНОСТИ в целом, но не об их конкретном типе. Вместо этого код полагается на полиморфизм времени выполнения и механику агентов для достижения очень гибкого ковариантного отношения между этими двумя отложенными классами и их потомками.

Нарисовать красный POLYGON на ETCHASKETCHнарисуйте красный POLYGON на GRAFFITI_WALLнарисуйте серый ПРЯМОУГОЛЬНИК на ETCHASKETCHнарисуйте серый ПРЯМОУГОЛЬНИК на GRAFFITI_WALLнарисуйте зеленый QUADRILATERAL на ETCHASKETCHнарисуйте зеленый QUADRILATERAL на GRAFFITI_WALLнарисуйте синий ПАРАЛЛЕЛОГРАММА на ETCHASKETCHнарисуйте синий ПАРАЛЛЕЛОГРАММА на GRAFFITI_WALLнарисуйте желтый POLYGON на ETCHASKETCHнарисуйте желтый POLYGON на GRAFFITI_WALLнарисуйте фиолетовый ПРЯМОУГОЛЬНИК на ETCHASKETCHнарисуйте фиолетовый ПРЯМОУГОЛЬНИК на GRAFFITI_WALL

Настроить

Прежде чем рассматривать ФОРМУ или ПОВЕРХНОСТЬ, нам нужно изучить разделенное на высоком уровне использование нашей двойной отправки.

Шаблон посетителя

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

В нашем примере ниже мы составляем список полиморфных объектов SHAPE, посещая каждый из них с помощью полиморфной ПОВЕРХНОСТИ, запрашивая ФОРМУ, которую нужно нарисовать на ПОВЕРХНОСТИ.

 1 	сделать 2 			- Печать фигур на поверхностях. 3 		местный 4 			l_shapes: ARRAYED_LIST [ФОРМА] 5 			l_surfaces: ARRAYED_LIST [ПОВЕРХНОСТЬ] 6 		делать 7 			Создайте l_shapes.сделать (6) 8 			l_shapes.расширять (Создайте {ПОЛИГОН}.make_with_color ("красный")) 9 			l_shapes.расширять (Создайте {ПРЯМОУГОЛЬНИК}.make_with_color ("серый"))10 			l_shapes.расширять (Создайте {ЧЕТЫРЕХСТОРОННИЙ}.make_with_color ("зеленый"))11 			l_shapes.расширять (Создайте {ПАРАЛЛЕЛОГРАММ}.make_with_color ("синий"))12 			l_shapes.расширять (Создайте {ПОЛИГОН}.make_with_color ("желтый"))13 			l_shapes.расширять (Создайте {ПРЯМОУГОЛЬНИК}.make_with_color ("фиолетовый"))14 15 			Создайте l_surfaces.сделать (2)16 			l_surfaces.расширять (Создайте {ETCHASKETCH}.сделать)17 			l_surfaces.расширять (Создайте {GRAFFITI_WALL}.сделать)18 19 			через l_shapes так как ic_shapes петля20 				через l_surfaces так как ic_surfaces петля21 					ic_surfaces.предмет.drawing_agent (ic_shapes.предмет.drawing_data_agent)22 				конец23 			конец24 		конец

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

Код делает полиморфный вызов {SURFACE} .draw косвенно через «drawing_agent», который является первым вызовом (отправкой) шаблона двойной отправки. Он передает косвенный и полиморфный агент (`drawing_data_agent '), позволяя нашему коду посетителя знать только о двух вещах:

  • Что такое агент рисования поверхности (например, al_surface.drawing_agent в строке № 21)?
  • Что такое агент данных рисования фигуры (например, al_shape.drawing_data_agent в строке № 21)?

Поскольку и SURFACE, и SHAPE определяют своих собственных агентов, наш код посетителя освобождается от необходимости знать, какой вызов следует сделать, полиморфно или иначе. Этот уровень косвенного обращения и разделения просто недостижим в других распространенных языках, таких как C, C ++ и Java, за исключением какой-либо формы отражения или перегрузки функций с сопоставлением сигнатур.

ПОВЕРХНОСТЬ

Внутри полиморфного вызова {SURFACE} .draw находится вызов агента, который становится вторым полиморфным вызовом или отправкой в ​​шаблоне двойной отправки.

 1 	отложенный класс 2 		ПОВЕРХНОСТЬ 3 	 4 	особенность {НИКТО} - Инициализация 5 	 6 		сделать 7 				- Инициализировать ток. 8 			делать 9 				drawing_agent := агент привлечь10 			конец11 	12 	особенность - Доступ13 14 		drawing_agent: ПРОЦЕДУРА [ЛЮБЫЕ, ТУПЛ [STRING, STRING]]15 				- Агент рисования Current.16 	17 	особенность {НИКТО} -- Реализация18 	19 		привлечь (a_data_agent: НАЗНАЧЕНИЕ [ЛЮБЫЕ, ТУПЛ, ТУПЛ [имя, цвет: STRING]])20 				- Нарисуйте a_shape на Current.21 			местный22 				l_result: ТУПЛ [имя, цвет: STRING]23 			делать24 				l_result := a_data_agent (Пустота)25 				Распечатать ("Нарисовать " + l_result.цвет + " " + l_result.имя + "на" + тип + "% N")26 			конец27 	28 		тип: STRING29 				- Введите имя Current.30 			отложенный конец31 	32 	конец

Аргумент агента в строке №19 и вызов в строке №24 являются полиморфными и независимыми. Агент отделен, потому что функция {SURFACE} .draw не знает, на каком классе основан `a_data_agent '. Невозможно определить, от какого класса был унаследован агент операции, поэтому он не обязательно должен происходить от SHAPE или одного из его потомков. Это явное преимущество агентов Eiffel перед единичным наследованием, динамическим и полиморфным связыванием других языков.

Агент динамически полиморфен во время выполнения, потому что объект создается в тот момент, когда он нужен, динамически, где версия объективированной подпрограммы определяется в это время. Единственное строго связанное знание - это тип результата сигнатуры агента, то есть именованный TUPLE с двумя элементами. Однако это конкретное требование основано на требовании включающей функции (например, в строке № 25 используются названные элементы TUPLE для выполнения функции «рисования» SURFACE), что необходимо и не удалось избежать (и, возможно, невозможно) .

Наконец, обратите внимание, что в ЛЮБОЙ клиент экспортируется только функция «drawing_agent»! Это означает, что код шаблона посетителя (который является ЕДИНСТВЕННЫМ клиентом этого класса) должен знать только об агенте, чтобы выполнить свою работу (например, используя агент в качестве функции, применяемой к посещаемым объектам).

ФОРМА

Класс SHAPE имеет основу (например, данные чертежа) для того, что нарисовано, возможно, на ПОВЕРХНОСТИ, но это не обязательно. И снова агенты обеспечивают косвенное обращение и агностики классов, необходимые для максимально возможной развязки ковариантных отношений с SHAPE.

Кроме того, обратите внимание на тот факт, что SHAPE предоставляет любому клиенту только «drawing_data_agent» как полностью экспортированную функцию. Следовательно, единственный способ взаимодействия с SHAPE, кроме создания, - это использовать средства «drawing_data_agent», который используется ЛЮБЫМ клиентом для косвенного и полиморфного сбора данных чертежа для SHAPE!

 1 	отложенный класс 2 		ФОРМА 3 	 4 	особенность {НИКТО} - Инициализация 5 	 6 		make_with_color (цвет: любить цвет) 7 				- Сделайте, используя a_color в качестве color. 8 			делать 9 				цвет := цвет10 				drawing_data_agent := агент drawing_data11 			обеспечить12 				color_set: цвет.same_string (цвет)13 			конец14 15 	особенность - Доступ16 	17 		drawing_data_agent: НАЗНАЧЕНИЕ [ЛЮБЫЕ, ТУПЛ, любить drawing_data]18 				- Агент данных для рисования.19 	20 	особенность {НИКТО} -- Реализация21 	22 		drawing_data: ТУПЛ [имя: любить имя; цвет: любить цвет]23 				- Данные, необходимые для рисования тока.24 			делать25 				Результат := [имя, цвет]26 			конец27 	28 		имя: STRING29 				- Имя объекта Current.30 			отложенный конец31 	32 		цвет: STRING33 				- Цвет тока.34 35 	конец

Пример классического космического корабля

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

Starship Enterprise меняет позицию с A-001 на A-002. Звездный корабль Enterprise предпринимает действия уклонения, избегая астероида `Rogue 1 '! Starship Enterprise меняет позицию с A-002 на A-003. «! Starship Enterprise передает команду ученых на звездолет Excelsior, когда они проходят! Starship Enterprise меняет позицию с A-003 на A-004. Звездный корабль Excelsior меняет позицию с A-003 на A-005. Звездный корабль Enterprise предпринимает действия уклонения, избегая астероида« Разбойник 3 '! Звездолет Эксельсиор находится рядом с космической станцией Deep Space 9 и может быть пристыкован. Звездный корабль Энтерпрайз меняет позицию с А-004 на А-005. Звездолет Энтерпрайз направляет научную группу на звездолет Эксельсиор, когда они проходят! Звездный корабль Энтерпрайз находится рядом с космической станцией Дип Пробел 9 и стыковочный.

Посетитель

Посетитель для классического примера космического корабля также имеет механизм двойной отправки.

 1 сделать 2 		- Позвольте объектам SPACESHIP посещать и перемещаться по вселенной. 3 	местный 4 		l_universe: ARRAYED_LIST [SPACE_OBJECT] 5 		l_enterprise, 6 		l_excelsior: ПРОСТРАНСТВО 7 	делать 8 		Создайте l_enterprise.make_with_name ("Предприятие", «А-001») 9 		Создайте l_excelsior.make_with_name («Эксельсиор», «А-003»)10 		Создайте l_universe.сделать (0)11 		l_universe.сила (l_enterprise)12 		l_universe.сила (Создайте {АСТЕРОИД}.make_with_name ("Разбойник 1", «А-002»))13 		l_universe.сила (Создайте {АСТЕРОИД}.make_with_name ("Разбойник 2", «А-003»))14 		l_universe.сила (l_excelsior)15 		l_universe.сила (Создайте {АСТЕРОИД}.make_with_name ("Разбойник 3", «А-004»))16 		l_universe.сила (Создайте {КОСМИЧЕСКАЯ СТАНЦИЯ}.make_with_name («Глубокий космос 9», «А-005»))17 		посещение (l_enterprise, l_universe)18 		l_enterprise.set_position («А-002»)19 		посещение (l_enterprise, l_universe)20 		l_enterprise.set_position («А-003»)21 		посещение (l_enterprise, l_universe)22 		l_enterprise.set_position («А-004»)23 		l_excelsior.set_position («А-005»)24 		посещение (l_enterprise, l_universe)25 		посещение (l_excelsior, l_universe)26 		l_enterprise.set_position («А-005»)27 		посещение (l_enterprise, l_universe)28 	конец29 особенность {НИКТО} -- Реализация30 посещение (a_object: SPACE_OBJECT; a_universe: ARRAYED_LIST [SPACE_OBJECT])31 		- `a_object 'посещает` a_universe'.32 	делать33 		через a_universe так как ic_universe петля34 			проверять прилагается {SPACE_OBJECT} ic_universe.предмет так как al_universe_object тогда35 				a_object.meet_agent.вызов ([al_universe_object.sensor_data_agent])36 			конец37 		конец38 	конец

Двойную отправку можно увидеть в строке № 35, где два косвенных агента работают вместе, чтобы обеспечить два ковариантных вызова, работающих в идеальном полиморфном согласовании друг с другом. «A_object» функции «visit» имеет «encryter_agent», который вызывается с данными датчика «sensor_data_agent», поступающими из «al_universe_object». Другой интересной частью этого конкретного примера является класс SPACE_OBJECT и его «встреча». ' особенность:

Действие посетителя

Единственные экспортируемые функции SPACE_OBJECT - это агенты для данных встреч и датчиков, а также возможность установить новое положение. Когда один объект (космический корабль) посещает каждый объект во вселенной, данные датчиков собираются и передаются посещающему объекту в его агенте встречи. Там данные датчика из sensor_data_agent (то есть элементы данных элемента данных TUPLE sensor_data, возвращенные запросом sensor_data_agent) сравниваются с текущим объектом, и на основе этой оценки принимается курс действий (см. SPACE_OBJECT ниже). Все остальные данные экспортируются в {NONE}. Это похоже на области видимости Private в C, C ++ и Java. В качестве неэкспортируемых функций данные и подпрограммы используются каждым SPACE_OBJECT только внутренне. Наконец, обратите внимание, что встречные вызовы `print 'не включают конкретную информацию о возможных классах-потомках SPACE_OBJECT! Единственное, что можно найти на этом уровне в наследовании, - это общие реляционные аспекты, полностью основанные на том, что можно узнать из атрибутов и процедур общего SPACE_OBJECT. Тот факт, что вывод "отпечатка" имеет смысл для нас, людей, на основе того, что мы знаем или представляем себе о звездных кораблях, космических станциях и астероидах, является просто логическим планированием или совпадением. SPACE_OBJECT не запрограммирован каким-либо конкретным знанием его потомков.

 1 отложенный класс 2 SPACE_OBJECT 3 особенность {НИКТО} - Инициализация 4 make_with_name (имя: любить имя; позиция: любить должность) 5     - Инициализировать ток с помощью `a_name 'и` a_position'. 6   делать 7     имя := имя 8     должность := позиция 9     sensor_data_agent := агент sensor_data10     meet_agent := агент встреча11   обеспечить12     name_set: имя.same_string (имя)13     position_set: должность.same_string (позиция)14   конец15 особенность - Доступ16 meet_agent: ПРОЦЕДУРА [ЛЮБЫЕ, ТУПЛ]17     - Агент для управления встречами с Current.18 sensor_data_agent: НАЗНАЧЕНИЕ [ЛЮБЫЕ, ТУПЛ, прилагается любить sensor_data_anchor]19     - Агент для возврата данных датчика тока.20 особенность - Настройки21 set_position (позиция: любить должность)22     - Установите позицию с помощью a_position.23   делать24     Распечатать (тип + " " + имя + "меняет позицию с" + должность + "к" + позиция + ".% N")25     должность := позиция26   обеспечить27     position_set: должность.same_string (позиция)28   конец29 особенность {НИКТО} -- Реализация30 встреча (a_sensor_agent: НАЗНАЧЕНИЕ [ЛЮБЫЕ, ТУПЛ, прилагается любить sensor_data_anchor])31     - Обнаруживать и сообщать о статусе столкновения Current с помощью a_radar_agent.32   делать33     a_sensor_agent.вызов ([Пустота])34     проверять прилагается {любить sensor_data_anchor} a_sensor_agent.last_result так как al_sensor_data тогда35       если не имя.same_string (al_sensor_data.имя) тогда36         если (должность.same_string (al_sensor_data.должность)) тогда37           если ((al_sensor_data.is_dockable и is_dockable) и38               (is_manned и al_sensor_data.is_manned) и39               (is_manueverable и al_sensor_data.is_not_manueverable)) тогда40             Распечатать (тип + " " + имя + " рядом " + al_sensor_data.тип + " " +41                 al_sensor_data.имя + "и стыкуется.% N")42           elseif ((is_dockable и al_sensor_data.is_dockable) и43                 (is_manned и al_sensor_data.is_manned) и44                 (is_manueverable и al_sensor_data.is_manueverable)) тогда45             Распечатать (тип + " " + имя + "передает команду ученых" + al_sensor_data.тип + " " +46                 al_sensor_data.имя + "пока они проходят!% N")47           elseif (is_manned и al_sensor_data.is_not_manned) тогда48             Распечатать (тип + " " + имя + "уклоняется, избегая" +49                 al_sensor_data.тип + " `" + al_sensor_data.имя + "'!% N")50           конец51         конец52       конец53     конец54   конец55 имя: STRING56     - Название тока.57 тип: STRING58     - Тип тока.59   отложенный60   конец61 должность: STRING62     - Положение тока.63 is_dockable: BOOLEAN64     - Может ли Current состыковаться с другим пилотируемым объектом?65   отложенный66   конец67 is_manned: BOOLEAN68     - Ток - это пилотируемый объект?69   отложенный70   конец71 is_manueverable: BOOLEAN72     - Ток может быть перемещен?73   отложенный74   конец75 sensor_data: прилагается любить sensor_data_anchor76     - Данные датчика тока.77   делать78       Результат := [имя, тип, должность, is_dockable, не is_dockable, is_manned, не is_manned, is_manueverable, не is_manueverable]79     конец80 81   sensor_data_anchor: съемный ТУПЛ [имя, тип, должность: STRING; is_dockable, is_not_dockable, is_manned, is_not_manned, is_manueverable, is_not_manueverable: BOOLEAN]82       - Якорь типа данных датчика Текущий.83 84 конец

Есть три класса-потомка SPACE_OBJECT:

SPACE_OBJECTАСТЕРОИДПРОСТРАНСТВОКОСМИЧЕСКАЯ СТАНЦИЯ

В нашем примере класс ASTEROID используется для предметов Rogue, SPACESHIP для двух звездных кораблей и SPACESTATION для Deep Space Nine. В каждом классе единственной специализацией является установка свойства "тип" и определенных свойств объекта. Имя указывается в подпрограмме создания, а также в качестве позиции. Например: Ниже приведен пример SPACESHIP.

 1 класс 2 ПРОСТРАНСТВО 3 наследовать 4 SPACE_OBJECT 5 Создайте 6 make_with_name 7 особенность {НИКТО} -- Реализация 8 тип: STRING = "Звездолет" 9   - <Предшественник>10 is_dockable: BOOLEAN = Правда11   - <Предшественник>12 is_manned: BOOLEAN = Правда13   - <Предшественник>14 is_manueverable: BOOLEAN = Правда15   - <Предшественник>16 конец

Итак, любой КОСМИЧЕСКИЙ КОСМОС в нашей Вселенной может стыковаться, укомплектован людьми и маневренным. Другие объекты, такие как астероиды, не относятся к этому. ПРОСТРАНСТВО, с другой стороны, может стыковаться и укомплектовано людьми, но не маневренным. Таким образом, когда один объект встречается с другим, он сначала проверяет, помещают ли их позиции в непосредственной близости друг от друга, и если да, то объекты взаимодействуют на основе их основных свойств. Обратите внимание, что объекты одного типа и name считаются одним и тем же объектом, поэтому взаимодействие логически запрещено.

Вывод из примера Эйфеля

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

Смотрите также

использованная литература

  1. ^ Простая техника работы с множественным полиморфизмом. В трудах OOPSLA '86, Системы объектно-ориентированного программирования, языки и приложения, страницы 347–349, ноябрь 1986 г. Напечатано как SIGPLAN Notices, 21 (11). ISBN  0-89791-204-7
  2. ^ Более эффективный C ++, Скотт Мейерс (Addison-Wesley, 1996)
  3. ^ «Использование динамического типа (Руководство по программированию на C #)». Сеть разработчиков Microsoft. Microsoft. 30 сен 2009. Получено 25 мая 2016. ... Разрешение перегрузки происходит во время выполнения, а не во время компиляции, если один или несколько аргументов в вызове метода имеют тип динамический ...