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

Из Википедии, бесплатной энциклопедии

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

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

Обзор [ править ]

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

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

Варианты использования [ править ]

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

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

Распространенная идиома [ править ]

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

Пример в Ruby [ править ]

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

class   Rectangle 
   def   display_on  (  port  ) 
     правильный 
     класса   . 
       основе   объекта 
        
       на   код 
        
       выбирает   # 
        
    
  

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

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

class   Rectangle 
   def   display_on  (  port  ) 
     # второй 
     порт  отправки .   display_rectangle  (  self  ) 
   end 
 end 

 class   Oval 
   def   display_on  (  port  ) 
     # второй 
     порт  отправки .   display_oval  (  self  ) 
   end 
 end 

 class   DisplayPort 
   def   display_rectangle  (  object  ) 
     #код для отображения прямоугольника на DisplayPort 
   end 
   def   display_oval  (  object  ) 
     #код для отображения овала на DisplayPort 
   end 
   # ... 
 end 

 class   PrinterPort 
   def   display_rectangle  (  object  ) 
     # код для отображения прямоугольника на PrinterPort 
   end 
   def   display_oval  (  объект  ) 
     # код для отображения овала на PrinterPort 
   end 
   # ... 
 end 

Двойная диспетчеризация в C++ [ править ]

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

класс   Космический корабль   {}; 
  класс   ApolloSpacecraft   :   общественный   космический корабль   {}; 

  class   Asteroid   { 
 public  : 
   virtual   void   CollideWith  (  SpaceShip  &  )   { 
     std  ::  cout   <<   "Астероид врезался в космический корабль  \n  "  ; 
    } 
   virtual   void   CollideWith  (  ApolloSpacecraft  &  )   { 
     std  ::  cout   <<   "Астероид столкнулся с космическим кораблем Apollo  \n  "  ; 
    } 
 }; 

  class   ExplodingAsteroid   :   public   Asteroid   { 
 public  : 
   void   CollideWith  (  SpaceShip  &  )   override   { 
     std  ::  cout   <<   "Взрывающийся астероид врезался в космический корабль  \n  "  ; 
    } 
   void   CollideWith  (  ApolloSpacecraft  &  )   override   { 
     std  ::  cout   <<   "Взрывающийся астероид столкнулся с космическим кораблем Apollo  \n  "  ; 
    } 
 }; 

Если у вас есть:

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

затем из-за перегрузки функции,

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

напечатает, соответственно, Asteroid hit a SpaceShip и Asteroid hit an ApolloSpacecraft, без использования какой-либо динамической отправки. Более того:

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

напечатаю ExplodingAsteroid hit a SpaceShip и ExplodingAsteroid hit an ApolloSpacecraft соответственно опять же без динамической отправки.

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

Астероид  &   theAsteroidReference   =   theExplodingAsteroid  ; 
  Справочник по астероидам  .   Столкнуться с  (  Космический корабль  );  
  Справочник по астероидам  .   CollideWith  (  космический корабль «Аполлон»  ); 

принты ExplodingAsteroid hit a SpaceShip и ExplodingAsteroid hit an ApolloSpacecraft, опять же, как и ожидалось. Однако следующий код не работает должным образом:

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

Желаемое поведение — связать эти вызовы с функцией, которая принимает theApolloSpacecraft в качестве аргумента, поскольку это экземпляр типа переменной, а это означает, что ожидаемый результат будет Asteroid hit an ApolloSpacecraft и ExplodingAsteroid hit an ApolloSpacecraft. Однако на самом деле результат Asteroid hit a SpaceShip и ExplodingAsteroid hit a SpaceShip. Проблема в том, что, хотя виртуальные функции в C++ выполняются динамически, перегрузка функций выполняется статически.

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

виртуальная   пустота   CollideWith  (  Asteroid  &   inAsteroid  )   { 
   inAsteroid  .   CollideWith  (  *  это  ); 
  } 

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

Космический корабль  &   theSpaceShipReference   =   theApolloSpacecraft  ; 
  Астероид  &   theAsteroidReference   =   theExplodingAsteroid  ; 
  ссылка на космический корабль  .   Столкнуться с  (  Астероид  ); 
  ссылка на космический корабль  .   CollideWith  (  theAsteroidReference  ); 

Он распечатывает Asteroid hit an ApolloSpacecraft и ExplodingAsteroid hit an ApolloSpacecraft, как и ожидалось. Ключ в том, что theSpaceShipReference.CollideWith(theAsteroidReference); во время выполнения выполняет следующее:

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

Двойная отправка в C# [ править ]

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

Двойная отправка в Эйфеле [ править ]

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

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

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

Выход [ править ]

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

нарисуйте  красный  МНОГОУГОЛЬНИК на ETCHASKETCH 
  нарисуйте  красный  МНОГОУГОЛЬНИК на GRAFFITI_WALL 
  нарисуйте  серый  ПРЯМОУГОЛЬНИК на ETCHASKETCH 
  нарисуйте  серый  ПРЯМОУГОЛЬНИК на GRAFFITI_WALL 
  нарисуйте  зеленый  ЧЕТЫРЕХСТОРОННИК на ETCHASKETCH 
  нарисуйте  зеленый  ЧЕТЫРЕХСТОРОННИЙ на GRAFFITI_WALL 
  нарисуйте  синий  ПАРАЛЛЕЛОГРАММ на ETCHASKETCH 
  нарисуйте  синий  ПАРАЛЛЕЛОГРАММ на GRAFFITI_WALL 
  нарисуйте  желтый  МНОГОУГОЛЬНИК на ETCHASKETCH 
  нарисуйте  желтый  МНОГОУГОЛЬНИК на GRAFFITI_WALL 
  нарисуйте  фиолетовый  ПРЯМОУГОЛЬНИК на ETCHASKETCH 
  нарисуйте  фиолетовый  ПРЯМОУГОЛЬНИК на GRAFFITI_WALL 
 

Настройка [ править ]

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

Шаблон посетителей [ править ]

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

В нашем примере ниже мы составляем список полиморфных объектов SHAPE, посещая каждый из них с помощью полиморфной SURFACE и прося отрисовать SHAPE на SURFACE.

	make 
			 — Печать фигур на поверхностях. 
		  local 
			 l_shapes  :   ARRAYED_LIST   [  SHAPE  ] 
			 l_surfaces  :   ARRAYED_LIST   [  SURFACE  ] 
		 создайте 
			  l_shapes  .   сделать   (  6  ) 
			 l_shapes  .   расширить   (  создать   {  ПОЛИГОН  }.  make_with_color   (  "красный"  )) 
			 l_shapes  .   расширить   (  создать   {  ПРЯМОУГОЛЬНИК  }.  make_with_color   (  "серый"  )) 
			 l_shapes  .   расширить   (  создать   {  QUADRILATERAL  }.  make_with_color   (  «зеленый»  )) 
			 l_shapes  .   расширить   (  создать   {  PARALLELOGRAM  }.  make_with_color   (  "синий"  )) 
			 l_shapes  .   расширить   (  создать   {  ПОЛИГОН  }.  make_with_color   (  "желтый"  )) 
			 l_shapes  .   расширить   (  создать   {  ПРЯМОУГОЛЬНИК  }.  make_with_color   (  "фиолетовый"  )) 

			 создать   l_surfaces  .   сделать   (  2  ) 
			 l_surfaces  .   расширить   (  создать   {  ETCHASKETCH  }.  make  ) 
			 l_surfaces  .   расширить   (  создать   {  GRAFFITI_WALL  }.  make  ) 

			 по   l_shapes,   как   ic_shapes,   l_surfaces 
				 зациклить   ,   как   ic_surfaces   , зациклить 
					 ic_surfaces  .   элемент  .   Drawing_agent   (  ic_shapes  .  item  .  Drawing_data_agent  ) 
				 конец 
			 конец 
		 конец 

Начнем с создания коллекции объектов 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 находится вызов агента, который становится вторым полиморфным вызовом или отправкой в ​​шаблоне двойной отправки.

	 отложенного   класса 
		 SURFACE 
	
	функция   {  NONE  }   -- Инициализация 
	
		 make 
				 -- Инициализировать текущий. 
			  do 
				 Drawing_agent   :=   агента   рисования 
			 завершения 
	
	 Функция   -- Доступ к 

		 Drawing_agent  :   PROCEDURE   [  ANY  ,   TUPLE   [  STRING  ,   STRING  ]] 
				 -- Агент рисования текущего. 
	
	  Feature   {  NONE  }   -- 
	
		 Отрисовка   реализации (  a_data_agent  :   FUNCTION   [  ANY  ,   TUPLE  ,   TUPLE   [  name  ,   color  :   STRING  ]]  ) 
				 -- Отрисовка `a_shape' в Current. 
			  local 
				 l_result  :   TUPLE   [  имя  ,   цвет  :   STRING  ] 
			 do 
				 l_result   :=   a_data_agent   (  Void  ) 
				 print   (  «draw a»   +   l_result  .  color   +   «»   +   l_result  .  name   +   «on»   +   type   +   «%N»  ) 
			 end 
	
		 type  :   STRING 
				 -- Введите имя текущего. 
			  отложенный   конец 
	
	 конец 

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

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

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

ФОРМА [ править ]

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

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

	 отложенного   класса 
		 SHAPE 
	
	функция   {  NONE  }   -- Инициализация 
	
		 make_with_color   (  a_color  :   Like   color  ) 
				 -- Сделать с `a_color' как `color'. 
			  do 
				 color   :=   a_color 
				 Drawing_data_agent   :=   агент   Drawing_data 
			 обеспечивает 
				 Color_Set  :   Color  .  Same_string   (  a_color  ) 
			  Конечная 

	 функция   — Доступ к 
	
		 Drawing_data_agent  :   FUNCTION   [  ЛЮБОЙ  ,   TUPLE  ,   как   Drawing_data  ] 
				 — Агент данных для рисования. 
	
	  Feature   {  NONE  }   -- Реализация 
	
		 Drawing_data  :   TUPLE   [  name  :   Like   name  ;    цвет  :   как   цвет  ] 
				 — Данные, необходимые для рисования тока. 
			  do 
				 Результат   :=   [  имя  ,   цвет  ] 
			 конечное 
	
		 имя  :   STRING 
				 — Имя объекта Current. 
			 отложенного   завершения 
	
		  Цвет  :   STRING 
				 — Цвет тока. 

	  конец 

Пример классического космического корабля [ править ]

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

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

Посетитель [ править ]

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

make 
		 -- Разрешить объектам SPACESHIP посещать и перемещаться во вселенной. 
	  local 
		 l_universe  :   ARRAYED_LIST   [  SPACE_OBJECT  ] 
		 l_enterprise  , 
		 l_excelsior  :   SPACESHIP 
	 создайте 
		  l_enterprise  .   make_with_name   (  "Enterprise"  ,   "A-001"  ) 
		 create   l_excelsior  .   make_with_name   (  "Excelsior"  ,   "A-003"  ) 
		 создать   l_universe  .   сделать   (  0  ) 
		 l_universe  .   сила   (  l_enterprise  ) 
		 l_universe  .   сила   (  создать   {  АСТЕРОИД  }.  make_with_name   (  "Разбойник 1"  ,   "А-002"  )) 
		 l_universe  .   сила   (  создать   {  АСТЕРОИД  }.  make_with_name   (  "Разбойник 2"  ,   "А-003"  )) 
		 l_universe  .   сила   (  l_excelsior  ) 
		 l_universe  .   сила   (  создать   {  АСТЕРОИД  }.  make_with_name   (  "Разбойник 3"  ,   "А-004"  )) 
		 l_universe  .   Force   (  создать   {  SPACESTATION  }.  make_with_name   (  "Deep Space 9"  ,   "A-005"  )) 
		 посетить   (  l_enterprise  ,   l_universe  ) 
		 l_enterprise  .   set_position   (  «A-002»  ) 
		 посетите   (  l_enterprise  ,   l_universe  ) 
		 l_enterprise  .   set_position   (  «A-003»  ) 
		 посетите   (  l_enterprise  ,   l_universe  ) 
		 l_enterprise  .   set_position   (  "A-004"  ) 
		 l_excelsior  .   set_position   (  "A-005"  ) 
		 посетить   (  l_enterprise  ,   l_universe  ) 
		 посетить   (  l_excelsior  ,   l_universe  ) 
		 l_enterprise  .   set_position   (  "A-005"  ) 
		 visit   (  l_enterprise  ,   l_universe  ) 
	 end 
 Feature   {  NONE  }   - 
 Визит для реализации  (  a_object  :   SPACE_OBJECT  ;   a_universe  :   ARRAYED_LIST   [  SPACE_OBJECT  ]  ) 
		 — `a_object' посещает `a_universe'. 
	  выполните 
		 через   a_universe   как   ic_universe   цикла 
			 проверку   {   SPACE_OBJECT  }   ic_universe  .   элемент   как   al_universe_object,   затем 
				 a_object  .   встреча_агент  .   вызов   (  [  al_universe_object  .  Sensor_data_agent  ]  ) 
			 конец 
		 конец 
	 конец 

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

Действия посетителя [ править ]

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

 отложенного   класса 
 SPACE_OBJECT 
функция   {  NONE  }   — Инициализация 
 make_with_name   (  a_name  :   подобное   имя  ;   a_position  :   подобное   положение  ) 
     — Инициализировать Current с помощью `a_name' и `a_position'. 
    do 
     name   :=   a_name 
     позиция   :=   a_position 
     датчик_данные_агент   :=   агент   датчик_данные 
     встреча_агент   :=   агента   встреча 
   обеспечения 
     имя_набор  :   имя  .   та же_строка   (  a_name  ) 
     Position_set  :   позиция  .  Same_string   (  a_position  ) 
    Конечная 
 функция   — Доступ к 
 агенту встречи  :   PROCEDURE   [  ЛЮБОЙ  ,   КОРОТОК  ] 
     — Агент для управления встречами с текущим. 
  Sensor_data_agent  :   FUNCTION   [  ЛЮБОЙ  ,   TUPLE  ,   прикрепленный   как   Sensor_data_anchor  ] 
     — Агент для возврата данных датчика Current. 
  Feature   — Настройки 
 set_position   (  a_position  :   Like   Position  ) 
     — Установите «position» с помощью «a_position». 
    do 
     print   (  type   +   ""   +   name   +   " меняет позицию с "   +   position   +   " на "   +   a_position   +   ".%N"  ) 
     позиция   :=   a_position 
   обеспечения 
     позиции_set  :   позиция  .   Same_string   (  a_position  ) 
   end 
 Feature   {  NONE  }   -- 
 Встреча   с реализацией (  a_sensor_agent  :   FUNCTION   [  ANY  ,   TUPLE  ,   прикреплен   как   Sensor_data_anchor  ]  ) 
     -- Обнаружение и отчет о состоянии конфликта Current с a_radar_agent. 
    сделать 
     a_sensor_agent .   вызов   (  [  Void  ]  ) 
     проверка   прикреплена   {  например,   Sensor_data_anchor  }   a_sensor_agent  .   Last_result   как   al_sensor_data   , тогда 
       , если   не   имя  .   Same_string   (  al_sensor_data.name  )  al_sensor_data.position  ,   then 
         if   (  position  .  Same_string   (  то  al_sensor_data  is_dockable  )   then 
           if   (  al_sensor_data  .  is_dockable   и   al_sensor_data  )   и 
               (  is_manned   и   .  .  is_manned  )   и 
               (  is_manueverable   и   (  напечатайте  is_not_manueverable  )   ) 
             тип   (  +   )   " "   +   name   +   " находится рядом с "   +   al_sensor_data  .  type   +   " "   + 
                 al_sensor_data  .  name   +   " и является закрепляемым.%N"  ) 
           elseif   ((  is_dockable   и   al_sensor_data  .  is_dockable  )   и 
                 (  is_manned   и   al_sensor_data  .  is_manned  )   и 
                 (  is_manueverable   и   al_sensor_data  .  is_manueverable  ))   затем 
             print   (  type   +   " "   +   name   +   " передает научную группу в "   +   al_sensor_data  .  type   +   " "   + 
                 al_sensor_data  .  name   +   " по мере прохождения!%N"  ) 
           elseif   (  is_manned   и   al_sensor_data  .  is_not_manned  ),   затем 
             напечатайте   (  type   +   " "   +   name   +   " предпринимает действия по уклонению, избегая "   + 
                 al_sensor_data  .  type   +   " `"   +   al_sensor_data  .  name   +   "'!%N"  ) 
           end 
         end 
       end 
     end 
   end 
 name  :   STRING 
     -- Name тока. 
  type  :   STRING 
     — Тип тока. 
    отложенная 
   конечная 
 позиция  :   STRING 
     -- Текущая позиция. 
  is_dockable  :   BOOLEAN 
    -- Можно ли «Текст» стыковаться с другим пилотируемым объектом? 
    deferred 
   end 
 is_manned  :   BOOLEAN 
     -- Является ли Current пилотируемым объектом? 
    deferred 
   end 
 is_manueverable  :   BOOLEAN 
     — Можно ли перемещать Current? 
    отложенный 
   конец 
 Sensor_data  :   прикреплен   как   Sensor_data_anchor 
     — данные датчика тока. 
    do 
       Результат   :=   [  имя  ,   тип  ,   позиция  ,   is_dockable  ,   not   is_dockable  ,   is_manned  ,   not   is_manned  ,   is_manueverable  ,   not   is_manueverable  ] 
     end 

   Sensor_data_anchor  :   detachable   TUPLE   [  имя  ,   тип  ,   позиция  :   STRING  ;    is_dockable  ,   is_not_dockable  ,   is_manned  ,   is_not_manned  ,   is_manueverable  ,   is_not_manueverable  :   BOOLEAN  ] 
       — Привязка типа данных датчика Current. 

  конец 

Существует три класса-потомка SPACE_OBJECT:

SPACE_OBJECT 
 АСТЕРОИД 
 КОСМИЧЕСКИЙ КОРАБЛЬ КОСМИЧЕСКАЯ 
 СТАНЦИЯ 

В нашем примере класс ASTEROID используется для предметов «Разбойник», SPACESHIP для двух звездных кораблей и SPACESTATION для Deep Space Nine. В каждом классе единственной специализацией является настройка признака «тип» и определенных свойств объекта. «Имя» указывается в процедуре создания, а также «позиция». Например: Ниже приведен пример КОСМИЧЕСКОГО КОРАБЛЯ.

класс 
 SPACESHIP 
 наследует 
 SPACE_OBJECT 
 create 
 make_with_name 
 Feature   {  NONE  }   -- 
 Тип  реализации :   STRING   =   "Starship" 
   -- <Precursor> 
 is_dockable  :   BOOLEAN   =   True 
   -- <Precursor> 
 is_manned  :   BOOLEAN   =   True 
   -- <Precursor> 
 is_manueverable  :   BOOLEAN   =   True 
   -- <Прекурсор> 
 конец 

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

Заключение по примеру Эйфеля [ править ]

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

См. также [ править ]

Ссылки [ править ]

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