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

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

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

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

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

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

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

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

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

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

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

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

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

class Rectangle
  def display_on(port)
    # selects the right code based on the object class
    case port
      when DisplayPort
        # code for displaying on DisplayPort
      when PrinterPort
        # code for displaying on PrinterPort
      when RemotePort
        # code for displaying on RemotePort
    end
  end
end

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

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

class Rectangle
  def display_on(port)
    # second dispatch
    port.display_rectangle(self)
  end
end

class Oval
  def display_on(port)
    # second dispatch
    port.display_oval(self)
  end
end

class DisplayPort
  def display_rectangle(object)
    # code for displaying a rectangle on a DisplayPort
  end
  def display_oval(object)
    # code for displaying an oval on a DisplayPort
  end
  # ...
end

class PrinterPort
  def display_rectangle(object)
    # code for displaying a rectangle on a PrinterPort
  end
  def display_oval(object)
    # code for displaying an oval on a PrinterPort
  end
  # ...
end

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

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

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};

class Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    std::cout << "Asteroid hit a SpaceShip\n";
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    std::cout << "Asteroid hit an ApolloSpacecraft\n";
  }
};

class ExplodingAsteroid : public Asteroid {
public:
  void CollideWith(SpaceShip&) override {
    std::cout << "ExplodingAsteroid hit a SpaceShip\n";
  }
  void CollideWith(ApolloSpacecraft&) override {
    std::cout << "ExplodingAsteroid hit an ApolloSpacecraft\n";
  }
};

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

Asteroid theAsteroid;
SpaceShip theSpaceShip;
ApolloSpacecraft theApolloSpacecraft;

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

theAsteroid.CollideWith(theSpaceShip); 
theAsteroid.CollideWith(theApolloSpacecraft);

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

ExplodingAsteroid theExplodingAsteroid;
theExplodingAsteroid.CollideWith(theSpaceShip); 
theExplodingAsteroid.CollideWith(theApolloSpacecraft);

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

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

Asteroid& theAsteroidReference = theExplodingAsteroid;
theAsteroidReference.CollideWith(theSpaceShip); 
theAsteroidReference.CollideWith(theApolloSpacecraft);

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

SpaceShip& theSpaceShipReference = theApolloSpacecraft;
theAsteroid.CollideWith(theSpaceShipReference);
theAsteroidReference.CollideWith(theSpaceShipReference);

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

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

virtual void CollideWith(Asteroid& inAsteroid) {
  inAsteroid.CollideWith(*this);
}

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

SpaceShip& theSpaceShipReference = theApolloSpacecraft;
Asteroid& theAsteroidReference = theExplodingAsteroid;
theSpaceShipReference.CollideWith(theAsteroid);
theSpaceShipReference.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 в целом, но не конкретный их тип. Вместо этого код полагается на полиморфизм во время выполнения и механику агентов для достижения очень гибкой ковариантной связи между этими двумя отложенными классами и их потомками.

draw a red POLYGON on ETCHASKETCH
draw a red POLYGON on GRAFFITI_WALL
draw a grey RECTANGLE on ETCHASKETCH
draw a grey RECTANGLE on GRAFFITI_WALL
draw a green QUADRILATERAL on ETCHASKETCH
draw a green QUADRILATERAL on GRAFFITI_WALL
draw a blue PARALLELOGRAM on ETCHASKETCH
draw a blue PARALLELOGRAM on GRAFFITI_WALL
draw a yellow POLYGON on ETCHASKETCH
draw a yellow POLYGON on GRAFFITI_WALL
draw a purple RECTANGLE on ETCHASKETCH
draw a purple RECTANGLE on GRAFFITI_WALL

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

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

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

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

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

	make
			-- Print shapes on surfaces.
		local
			l_shapes: ARRAYED_LIST [SHAPE]
			l_surfaces: ARRAYED_LIST [SURFACE]
		do
			create l_shapes.make (6)
			l_shapes.extend (create {POLYGON}.make_with_color ("red"))
			l_shapes.extend (create {RECTANGLE}.make_with_color ("grey"))
			l_shapes.extend (create {QUADRILATERAL}.make_with_color ("green"))
			l_shapes.extend (create {PARALLELOGRAM}.make_with_color ("blue"))
			l_shapes.extend (create {POLYGON}.make_with_color ("yellow"))
			l_shapes.extend (create {RECTANGLE}.make_with_color ("purple"))

			create l_surfaces.make (2)
			l_surfaces.extend (create {ETCHASKETCH}.make)
			l_surfaces.extend (create {GRAFFITI_WALL}.make)

			across l_shapes as ic_shapes loop
				across l_surfaces as ic_surfaces loop
					ic_surfaces.item.drawing_agent (ic_shapes.item.drawing_data_agent)
				end
			end
		end

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

	deferred class
		SURFACE
	
	feature {NONE} -- Initialization
	
		make
				-- Initialize Current.
			do
				drawing_agent := agent draw
			end
	
	feature -- Access

		drawing_agent: PROCEDURE [ANY, TUPLE [STRING, STRING]]
				-- Drawing agent of Current.
	
	feature {NONE} -- Implementation
	
		draw (a_data_agent: FUNCTION [ANY, TUPLE, TUPLE [name, color: STRING]])
				-- Draw `a_shape' on Current.
			local
				l_result: TUPLE [name, color: STRING]
			do
				l_result := a_data_agent (Void)
				print ("draw a " + l_result.color + " " + l_result.name + " on " + type + "%N")
			end
	
		type: STRING
				-- Type name of Current.
			deferred end
	
	end

Аргумент агента в строке № 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!

	deferred class
		SHAPE
	
	feature {NONE} -- Initialization
	
		make_with_color (a_color: like color)
				-- Make with `a_color' as `color'.
			do
				color := a_color
				drawing_data_agent := agent drawing_data
			ensure
				color_set: color.same_string (a_color)
			end

	feature -- Access
	
		drawing_data_agent: FUNCTION [ANY, TUPLE, like drawing_data]
				-- Data agent for drawing.
	
	feature {NONE} -- Implementation
	
		drawing_data: TUPLE [name: like name; color: like color]
				-- Data needed for drawing of Current.
			do
				Result := [name, color]
			end
	
		name: STRING
				-- Object name of Current.
			deferred end
	
		color: STRING
				-- Color of Current.

	end

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

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

Starship Enterprise changes position from A-001 to A-002.
Starship Enterprise takes evasive action, avoiding Asteroid `Rogue 1'!
Starship Enterprise changes position from A-002 to A-003.
Starship Enterprise takes evasive action, avoiding Asteroid `Rogue 2'!
Starship Enterprise beams a science team to Starship Excelsior as they pass!
Starship Enterprise changes position from A-003 to A-004.
Starship Excelsior changes position from A-003 to A-005.
Starship Enterprise takes evasive action, avoiding Asteroid `Rogue 3'!
Starship Excelsior is near Space Station Deep Space 9 and is dockable.
Starship Enterprise changes position from A-004 to A-005.
Starship Enterprise beams a science team to Starship Excelsior as they pass!
Starship Enterprise is near Space Station Deep Space 9 and is dockable.

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

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

make
		-- Allow SPACESHIP objects to visit and move about in a universe.
	local
		l_universe: ARRAYED_LIST [SPACE_OBJECT]
		l_enterprise,
		l_excelsior: SPACESHIP
	do
		create l_enterprise.make_with_name ("Enterprise", "A-001")
		create l_excelsior.make_with_name ("Excelsior", "A-003")
		create l_universe.make (0)
		l_universe.force (l_enterprise)
		l_universe.force (create {ASTEROID}.make_with_name ("Rogue 1", "A-002"))
		l_universe.force (create {ASTEROID}.make_with_name ("Rogue 2", "A-003"))
		l_universe.force (l_excelsior)
		l_universe.force (create {ASTEROID}.make_with_name ("Rogue 3", "A-004"))
		l_universe.force (create {SPACESTATION}.make_with_name ("Deep Space 9", "A-005"))
		visit (l_enterprise, l_universe)
		l_enterprise.set_position ("A-002")
		visit (l_enterprise, l_universe)
		l_enterprise.set_position ("A-003")
		visit (l_enterprise, l_universe)
		l_enterprise.set_position ("A-004")
		l_excelsior.set_position ("A-005")
		visit (l_enterprise, l_universe)
		visit (l_excelsior, l_universe)
		l_enterprise.set_position ("A-005")
		visit (l_enterprise, l_universe)
	end
feature {NONE} -- Implementation
visit (a_object: SPACE_OBJECT; a_universe: ARRAYED_LIST [SPACE_OBJECT])
		-- `a_object' visits `a_universe'.
	do
		across a_universe as ic_universe loop
			check attached {SPACE_OBJECT} ic_universe.item as al_universe_object then
				a_object.encounter_agent.call ([al_universe_object.sensor_data_agent])
			end
		end
	end

Двойную отправку можно увидеть в строке № 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 не запрограммирован с учетом каких-либо конкретных знаний о его потомках.

deferred class
SPACE_OBJECT
feature {NONE} -- Initialization
make_with_name (a_name: like name; a_position: like position)
    -- Initialize Current with `a_name' and `a_position'.
  do
    name := a_name
    position := a_position
    sensor_data_agent := agent sensor_data
    encounter_agent := agent encounter
  ensure
    name_set: name.same_string (a_name)
    position_set: position.same_string (a_position)
  end
feature -- Access
encounter_agent: PROCEDURE [ANY, TUPLE]
    -- Agent for managing encounters with Current.
sensor_data_agent: FUNCTION [ANY, TUPLE, attached like sensor_data_anchor]
    -- Agent for returning sensor data of Current.
feature -- Settings
set_position (a_position: like position)
    -- Set `position' with `a_position'.
  do
    print (type + " " + name + " changes position from " + position + " to " + a_position + ".%N")
    position := a_position
  ensure
    position_set: position.same_string (a_position)
  end
feature {NONE} -- Implementation
encounter (a_sensor_agent: FUNCTION [ANY, TUPLE, attached like sensor_data_anchor])
    -- Detect and report on collision status of Current with `a_radar_agent'.
  do
    a_sensor_agent.call ([Void])
    check attached {like sensor_data_anchor} a_sensor_agent.last_result as al_sensor_data then
      if not name.same_string (al_sensor_data.name) then
        if (position.same_string (al_sensor_data.position)) then
          if ((al_sensor_data.is_dockable and is_dockable) and
              (is_manned and al_sensor_data.is_manned) and
              (is_manueverable and al_sensor_data.is_not_manueverable)) then
            print (type + " " + name + " is near " + al_sensor_data.type + " " +
                al_sensor_data.name + " and is dockable.%N")
          elseif ((is_dockable and al_sensor_data.is_dockable) and
                (is_manned and al_sensor_data.is_manned) and
                (is_manueverable and al_sensor_data.is_manueverable)) then
            print (type + " " + name + " beams a science team to " + al_sensor_data.type + " " +
                al_sensor_data.name + " as they pass!%N")
          elseif (is_manned and al_sensor_data.is_not_manned) then
            print (type + " " + name + " takes evasive action, avoiding " +
                al_sensor_data.type + " `" + al_sensor_data.name + "'!%N")
          end
        end
      end
    end
  end
name: STRING
    -- Name of Current.
type: STRING
    -- Type of Current.
  deferred
  end
position: STRING
    -- Position of Current.
is_dockable: BOOLEAN
    -- Is Current dockable with another manned object?
  deferred
  end
is_manned: BOOLEAN
    -- Is Current a manned object?
  deferred
  end
is_manueverable: BOOLEAN
    -- Is Current capable of being moved?
  deferred
  end
sensor_data: attached like sensor_data_anchor
    -- Sensor data of Current.
  do
      Result := [name, type, position, is_dockable, not is_dockable, is_manned, not is_manned, is_manueverable, not is_manueverable]
    end

  sensor_data_anchor: detachable TUPLE [name, type, position: STRING; is_dockable, is_not_dockable, is_manned, is_not_manned, is_manueverable, is_not_manueverable: BOOLEAN]
      -- Sensor data type anchor of Current.

end

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

SPACE_OBJECT
ASTEROID
SPACESHIP
SPACESTATION

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

class
SPACESHIP
inherit
SPACE_OBJECT
create
make_with_name
feature {NONE} -- Implementation
type: STRING = "Starship"
  -- <Precursor>
is_dockable: BOOLEAN = True
  -- <Precursor>
is_manned: BOOLEAN = True
  -- <Precursor>
is_manueverable: BOOLEAN = True
  -- <Precursor>
end

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

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

Что касается двойной диспетчеризации, 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 г. . ... Разрешение перегрузки происходит во время выполнения, а не во время компиляции, если один или несколько аргументов в вызове метода имеют тип динамический...