Двойная отправка
Полиморфизм |
---|
Специальный полиморфизм |
Параметрический полиморфизм |
Подтипирование |
В обеспечения разработке программного двойная диспетчеризация — это особая форма множественной диспетчеризации и механизм, который отправляет вызов функции различным конкретным функциям в зависимости от типов времени выполнения двух объектов, участвующих в вызове. В большинстве объектно-ориентированных систем конкретная функция, вызываемая из вызова функции в коде, зависит от динамического типа одного объекта, и поэтому они известны как вызовы одиночной диспетчеризации или просто вызовы виртуальных функций .
Дэн Ингаллс первым описал, как использовать двойную диспетчеризацию в Smalltalk , назвав это множественным полиморфизмом . [1]
Обзор
[ редактировать ]Общая решаемая проблема заключается в том, как отправить сообщение различным методам в зависимости не только от получателя, но и от аргументов.
С этой целью такие системы, как CLOS, реализуют множественную диспетчеризацию . Двойная диспетчеризация — еще одно решение, которое постепенно уменьшает полиморфизм в системах, не поддерживающих множественную диспетчеризацию.
Варианты использования
[ редактировать ]Двойная диспетчеризация полезна в ситуациях, когда выбор вычислений зависит от типов их аргументов во время выполнения. Например, программист может использовать двойную диспетчеризацию в следующих ситуациях:
- Сортировка смешанного набора объектов: алгоритмы требуют, чтобы список объектов был отсортирован в некотором каноническом порядке. Решение о том, стоит ли один элемент перед другим, требует знания обоих типов и, возможно, некоторого подмножества полей.
- Адаптивные алгоритмы столкновений обычно требуют, чтобы столкновения между разными объектами обрабатывались разными способами. Типичным примером является игровая среда, где столкновение космического корабля с астероидом рассчитывается иначе, чем столкновение космического корабля с космической станцией. [2]
- Алгоритмы рисования , которые требуют, чтобы точки пересечения перекрывающихся спрайтов отображались другим способом.
- Системы управления персоналом могут распределять разные типы работ разным сотрудникам. А
schedule
Алгоритм, которому дан объект человека, типизированный как бухгалтер, и объект работы, типизированный как инженерное дело, отклоняет планирование этого человека для этой работы. - Системы обработки событий , которые используют как тип события, так и тип объекта-рецептора для вызова правильной процедуры обработки событий.
- Системы замков и ключей , в которых существует множество типов замков и множество типов ключей, и каждый тип ключа открывает несколько типов замков. Вам необходимо знать не только типы задействованных объектов, но и подмножество «информации о конкретном ключе, которая важна для определения того, открывает ли конкретный ключ конкретный замок», различается для разных типов замков.
Распространенная идиома
[ редактировать ]Общая идиома, как и в примерах, представленных выше, заключается в том, что выбор соответствующего алгоритма основан на типах аргументов вызова во время выполнения. Таким образом, вызов сопряжен со всеми обычными дополнительными затратами на производительность, связанными с динамическим разрешением вызовов, обычно больше, чем в языке, поддерживающем отправку только одного метода. В C++ , например, вызов динамической функции обычно разрешается путем одного вычисления смещения , что возможно, поскольку компилятор знает расположение функции в таблице методов объекта и поэтому может статически вычислить смещение. В языке, поддерживающем двойную диспетчеризацию, это немного дороже, поскольку компилятор должен сгенерировать код для расчета смещения метода в таблице методов во время выполнения, тем самым увеличивая общую длину пути инструкции (на величину, которая, вероятно, будет не более общее количество вызовов функции, которое может быть не очень значительным).
Пример на Руби
[ редактировать ]Распространенным вариантом использования является отображение объекта на порту дисплея, которым может быть экран, принтер или что-то еще, чего еще не существует. Это наивная реализация того, как обращаться с этими различными медиа.
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);
во время выполнения выполняет следующее:
theSpaceShipReference
является ссылкой, поэтому C++ ищет правильный метод в vtable. В этом случае он вызоветApolloSpacecraft::CollideWith(Asteroid&)
.- В пределах
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 позволяет дизайнеру и программисту дополнительно удалить уровень прямого знания объекта-объекта, отделив подпрограммы классов от их классов, сделав их агентами, а затем передав этих агентов вместо создания прямых функций объекта. звонки. Агенты также имеют определенные подписи и возможные результаты (в случае запросов), что делает их идеальными средствами статической проверки типов , не отказываясь от конкретных деталей объекта. Агенты полностью полиморфны, поэтому результирующий код содержит только те конкретные знания, которые необходимы для выполнения локальной работы. В противном случае не будет дополнительной нагрузки на обслуживание, поскольку знание конкретных внутренних функций класса будет распределено по множеству ковариантных объектов. Это обеспечивается использованием и механикой агентов. Одним из возможных недостатков использования агентов является то, что агент требует больше вычислительных затрат, чем его аналог с прямым вызовом. Имея это в виду, никогда не следует предполагать использование агентов в двойной рассылке и их применение в шаблонах посещений. Если можно ясно увидеть ограничение дизайна в отношении области типов классов, которые будут участвовать в ковариантных взаимодействиях, то прямой вызов является более эффективным решением с точки зрения вычислительных затрат. Однако если ожидается, что классовая область участвующих типов будет существенно расти или отличаться, то агенты представляют собой отличное решение для уменьшения нагрузки на обслуживание в схеме двойной отправки.
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Простой метод обработки множественного полиморфизма. В Proceedings of OOPSLA '86, «Системы объектно-ориентированного программирования, языки и приложения», страницы 347–349, ноябрь 1986 г. Напечатано как уведомления SIGPLAN, 21 (11). ISBN 0-89791-204-7
- ^ Более эффективный C++ Скотта Мейерса (Аддисон-Уэсли, 1996).
- ^ «Использование динамического типа (Руководство по программированию на C#)» . Сеть разработчиков Microsoft . Майкрософт. 30 сентября 2009 г. Проверено 25 мая 2016 г. .
... Разрешение перегрузки происходит во время выполнения, а не во время компиляции, если один или несколько аргументов в вызове метода имеют тип динамический...
Эта статья нуждается в дополнительных цитатах для проверки . ( август 2008 г. ) |