Двойная отправка
Полиморфизм |
---|
Специальный полиморфизм |
Параметрический полиморфизм |
Подтипирование |
В обеспечения разработке программного двойная диспетчеризация — это особая форма множественной диспетчеризации и механизм, который отправляет вызов функции различным конкретным функциям в зависимости от типов времени выполнения двух объектов, участвующих в вызове. В большинстве объектно-ориентированных систем конкретная функция, вызываемая из вызова функции в коде, зависит от динамического типа одного объекта, и поэтому они известны как вызовы одиночной диспетчеризации или просто вызовы виртуальных функций .
Дэн Ингаллс первым описал, как использовать двойную диспетчеризацию в 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);
во время выполнения выполняет следующее:
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 г. ) |