Анимация
JavaScript
|
Главная Библионтека на любое количество объектов. Однако в большинстве ситуаций можно обойтись двойной диспетчеризацией, непосредственно воспользовавшись библиотекой Loki. 11.1. Что такое мультиметоды? в языке С++ полиморфизм, по сушеству, означает, что вызов данной функции связан с разными реализациями и зависит от статического или динамического контекста. В языке С++ реализованы два типа полиморфизма. • Статический полиморфизм (compile-time ро1утоф1115т), осуществляемый С помощью перегрузки и шаблонных функций. • Динамический полиморфизм (run-time ро1утоф1115т)м, реализуемый виртуальными функциями. Перефузка - это простой вид полиморфизма, позволяющий нескольким одноименным функциям существовать в одной и той же области видимости. Если эти функции имеют разные списки парамефов, компилятор может различать их во время компиляции. Перефузка представляет собой удобную синтаксическую конструкцию, позволяющую сократить текст профаммы. Шаблонные функции являются основой статического механизма диспетчеризации. Они осуществляют более сложный статический полиморфизм. Имя виртуальной функции связывается с ее конкретной реализацией во время выполнения программы в зависимости от динамического типа объекта, к которому она применяется. Посмофим, как масштабируются эти фи вида полиморфизма для нескольких объектов. Для перефуженных и шаблонных функций это происходит вполне естественно. Оба механизма допускают использование нескольких парамефов, а выбор функции производится на основе запутанных статических правил. К сожалению, виртуальные функции - единственный механизм, реализующий динамический полиморфизм в языке С++, - могут работать только с одним объектом. Даже синтаксис вызова виртуальной функции - obj .Fun (.аргументы) - отдает предпочтение объекту obj перед аргументами. (Фактически объект obj является лишь одним из аргументов функции Fun, доступ к которому осуществляется с помощью указателя *this. В языке Dylan, например, оператор "точка" является лишь одним из многих конкретных воплощений общего механизма вызова функции.) Мы будем называть мультиметодами, или множественной диспетчеризацией, механизм, связывающий вызов с разными конкретными функциями в зависимости от динамических типов нескольких объектов, участвующих в вызове. Поскольку статический мультиобъект-ный полиморфизм уже существует, остается только реализовать его динамический аналог. 11.2. Когда нужны мультиметоды Определить, когда следует использовать мультиметоды, очень просто. Допустим, в профамме есть операция, манипулирующая несколькими полиморфными объектами Автоматическое преобразование типов можно было бы квалифицировать как простейший вид полиморфизма, поскольку оно, например, позволяет вызывать функцию sin::sin с параметром, имеюшим тип int, хотя изначально параметр этой функции имеет тип double. Однако это мнимый полиморфизм, поскольку к параметрам обоих типов применяется одна и та же функция. с помошью указателей или ссылок на их базовые классы. Необходимо модифицировать операцию в соответствии с динамическими типами этих объектов. Столкновения (collisions) представляют собой типичную категорию проблем, которые лучше всего решать с помощью мультиметодов. Предположим, что мы разрабатываем видеоигру, в которой движущиеся объекты выводятся из абстрактного класса GameObject. Нам хотелось бы, чтобы их столкновение зависело от типов сталкивающихся объектов: космического корабля с астероидом, корабля с космической станцией или астероида со станцией. Допустим, что мы хотим выделять области перекрытия двух нарисова?шых объектов. Напишем программу для рисования, позволяющую пользователю определять прямоугольники, круги, эллипсы, многоугольники и другие фигуры. В основу разработки положим классический объектно-ориентированный подход: определим абстрактный класс shape и вьгеедем все конкретные фигуры из него. Затем будем управлять рисованием с помощью набора указателей на объекты, производных от класса shape. Тут к нам подходит клиент и просит предусмотреть следующее: если две фигуры пересекутся, область их пересечения нужно нарисовать иначе, чем сами фигуры, например, заштриховать (рис. 11.1). Рис. 11.1. Штриховка пересечения двух фигур Разработать один алгоритм, заштриховывающий любое пересечение, сложно. Например, алгоритм для штриховки пересечения эллипса и прямоугольника намного сложнее, чем алгоритм для штриховки пересечения двух прямоугольников. Кроме того, чрезмерно общий алгдритм (например, на уровне работы с пикселями) будет слишком неэффективен, поскольку некоторые пересечения (скажем, двух прямоугольников) тривиальны. Здесь нужен набор алгоритмов, специализированных для каждой пары фигур, например, прямоугольник-прямоугольник, прямоугольник-многоугольник, многоугольник-многоугольник, эллипс-многоугольник и эллипс-эллипс, в ходе выполнения программы, когда пользователь перемешает фигуры по экрану, нужно выбрать правильный алгоритм, который бы закрашивал пересечение фигур достаточно быстро. Поскольку манипуляции фигурами осуществляются с помощью указателей на класс shape, у нас нет информации о типах объектов, позволяющей выбирать нужный алгоритм. Придется работать только с указателями на класс shape. Поскольку в каждом пересечении участвуют два объекта, простые виртуальные функции не решат нашу задачу. Следует применить двойную диспетчеризацию. Этот пример и имена позаимствованы из книги Скотта Мейерса "More Effective С++" (1996а), задача 31. 11.3. Двойное переключение по типу: грубый подход Проще всего реализовать двойную диспетчеризацию на основе двойного переключения по типу (double switch-on-type). Для этого нужно попытаться последовательно выполнить динамическое приведение типа первого объекта к каждому из типов объектов, которые могут стоять в левой части выражения. Для каждой ветви нужно сделать то же самое со вторым аргументом. После обнаружения типов для обоих объектов становится ясно, какую функцию следует вызвать. Разные алгоритмы закраски пересечения фигур void DoHatchArealCRectangle&, Rectangle*); void DoHatchAreaZ(Rectangle*, Ellipse*); void DoHatchArea3(Rectangle*, Poly*); void DoHatchArea4(Ellipse*, Poly*); void DoHatchAreaS(Ellipse*. Ellipse*); void DoHatchArea6(Poly*, Poly*); void DoubleDispatch(shape* Ihs, shape* rhs) { if (Rectangle* pi = dynamic cast<Rectangle*>(*lhs)) if (Rectangle* p2 = dynamic cast<Rectangle*>(*rhs)) DoHatchAreal(*pl, *p2); else if (Ellipse p2 = dynamic cast<Ellipse*>(*rhs)) D0HatchArea2(*pl, *p2); else if (Poly p2 = dynamic cast<Poly*>(*rhs)) D0HatchArea3(*pl, *p2); else ЕггогСнеопределенное пересечение"); else if (Ellipse* pi = dynamic cast<Ellipse*>(*lhs)) { if (Rectangle* p2 = dynamic cast<Rectangle*>(*rhs)) DoHatchArea2(*p2, *pl); else if (Ellipse p2 = dynamic cast<Ellipse*>(&rhs)) D0HatchArea5(*pl, *p2); else if (Poly p2 = dynamic cast<Poly*>(&rhs)) DoHatchArea4(*pl, *p2); el se Еггог("неопределенное пересечение"); else if (Poly* pi = dynamic cast<Poly*)(*lhs)) { if (Rectangle* p2 = dynamic cast<Rectangle*>(&rhs)) DoHatchArea2(*p2, *pl); else if (Ellipse p2 = dynamic cast<Ellipse*>(*rhs)) DoHatchArea4(*p2, *pl); else if (Poly p2 = dynamic cast<Poly*>(*rhs)) DoHatchArea6(*pl, *p2); else Еггог("неопределенное пересечение"); else { Еггог("неопределенное пересечение"); 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 [ 88 ] 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |