А. Александреску - Современное проектирование на C++ (1119444), страница 60
Текст из файла (страница 60)
(Класс цепс(пеагнзегагсйу использует второЙ аргумент для генерации линейной (мппкьйаред) иерархии наследования, изображенной на рис. 3.6.) семр)асе <с)аьв сопсгесеягодосс, с)азз вазе> с)аьв Орнеияастогуппдс : риЫз с вазе ( суредеФ сурепаме вазе:гягодиссьззс вазеягодоссь1зс; ргосессед: суреде1 сурепаме вазеягодиссьззс::таз) ягодисссззс; риЫзс: суредег сурепаме вазеягодоссьззс::неад дЬзсгассягодцсс; сопсгесеягодцсс* Оосгеасе(туре2туре<дЬзсгассягодцсс>) геСогп пеи Сопсгетеягодцст," Чтобы определить, какое абстрактное изделие следует создать, класс Орнеияассогуцпз с должен выполнять только одно вычисление типа.
Каждая конкретизация класса Ормеияастогуппзт является компонентом некоей "пищевой" цепи. Каждая конкретизация класса Орнеигастогуппдс "откусывает" голову списка изделий, замещая соответствующую функцию Оосгеасе и передавая "безголовыи" список ягодцссьдзс вниз по иерархии классов.
Таким образом, конкретизация класса Ормеигастогуопз С, находящаяся на вершине иерарХии (ораву за кЛассом АЬзсгассепемуяассогу), реализует функцию Оосгеасе(туре2туре<во)дзег>), а конкретизация, находящаяся на дне иерархии, реализует функцию Оосгеасе(туре2туре<вцрегмопзсег>). Итак, попробуем разобраться, почему класс Орнеикассогуопзс занимает такое высокое положение в иерархии. Во-первых, класс Орнеияассогуцпзс импортирует тип 246 Часть П.
Компоненты РгодцссОзс из базового класса и присваивает ему новое имя вазеггодиссс1зс. (Просмотрев определение класса АЬзсгассгассогу, легко понять, что он на самом деле экспортирует тип ггодцссЫзс.) Абстрактное изделие, реализованное классом орнепгассогуоп)с, представляет собой голову списка вазеггодцссОзс в соответствии с определением класса АЬзсгассггодцсс, В заключение класс орнепгассогуцп)с реэкспортирует класс вазеггодцссызс::та11 в качестве типа ггодцссОзс. Оставшаяся часть списка передается вниз по иерархии. Обратите внимание на то, что функция ориепгассогуоп)с::оосгеасе не возврашает указатель на объект класса АЬзсгассггодисс„как это делает ее настояший прототип.
Вместо этого она возврашает указатель на объект класса сопсгесеггодисс. Можно ли это по-прежнему квалифицировать как реализацию чисто виртуальной функции? Да, благодаря кавариантным типам возвращаемых значений (сочапап1 ге1цгп 1урез). Язык С++ позволяет возврашать указатель на объект нраизваднага класса. В этом есть глубокий смысл. Благодаря этим типам программист либо знает точный тип конкретной фабрики, получая максимум информации, либо знает только ее базовый тип, получая меньший объем информации. Класс сопсгесегассогу должен генерировать иерархию, используя класс оепОпеагн1егагсну. Его реализация вполне очевилна.
севр1асе < с1ава АЬзсгассгасс, севр1асе <с1азз, с1аэв> с1азз сгеасог огнепгассогуцп(с; с1аьв тОзс сурепаве АЬзсгассгасс::ггодцссОзс > с1азз сопсгесегассогу : рцЬ)зс сепОпеагн1егагсну< сурепаве тшяечегзе<тОзс>;;яези1с, сгеасог, Аьзсгассгасс> суредет сурепаве АЬзсгассгасс::ггодцссОзс ггодцссЫзс; суредет тОзс сопсгесеггодцссйзс; Иерархия классов, генерируемая классом оепО пеагн(егагсну для класса сопсгесегассогу, показана на рис. 9.3. Здесь скрывается одна маленькая хитрость: класс сопсгесегассогу должен переворачивать список конкретных изделий, передавая его классу ОепЫпеагн1егагсну. Зачем? Для этого нужно вернуться к рис. 3.6, на котором показано, как класс оепОпеагн(егагсЬу генерирует иерархию.
Этот класс передает типы из списка типов в шаблонный аргумент цп1с снизу вверх. Первый элемент списка типов передается конкретизации класса оп(с, находящейся на дне иерархии классов. Однако класс ориеыгассогуцп(с реализует перегрузку функции оосгеасе сверху вниз. Следовательно, класс Сопсгесегассогу должен переворачивать список тЫзс, используя статический алгоритм тш:вече гзе (глава 3), и только после этого передавать его классу оепОпеагн)егагсну. Если вы все еше считаете классы АЬзсгассгассогу и сопсгесегассогу сложными и запутанными, потерпите, Так кажется потому, что классы небрежно обрашаются со списками типов. Списки типов представляю~ собой новое понятие, и, чтобы привыкнуть к нему, требуется время.
Если вы будете считать списки типов подобием "черного яшика" — "списки типов по отношению к типам играют ту же роль, что и обычные списки по отношению к значениям", — реализация классов сразу станет Глава 9. Шаблон АЬв1гас1 Гвс1огу 247 понятнее. Если уж вы иа самом деле решили использовать списки типов, то ваши воз- можности стануг поистине неограниченными.
Нс верите? Читайте дальше. ми(гас!епепгурасгогу Ориеерасаггуипа<ЗИ(увохаег, Аьа!гас(Епетурас(огу> Оепцпеагнегагсьу<ггрецзт ! (Яи)узо!с|ег), Ормеерас|ауцм|,Аьягас|епетурас|огу> Орнеесгеа!ог<зиЬгмопа1ег, Оепцпеагн|егагсьу<'ГигеОят ! (Яи(уяо(авг), Орнеера<1огуцпг(, Аьагга<1епепгурас(оу» Оепцпеагн|егагспу<т)грецзт2(Зг(Игмопа1ег, яи(узо(а|аг), Орнеесгеа\ог Аьаьасгепепгурасгогу> Ориеърас1огуцпп<86уворегМопиег, Оепь)пеагН|егагспу <гягеъ(Ят2(зи(умопагаг, ягиуяо!д|ег), Орнеесгеа(огАьаггасгепепгурасгогу» ОепцпеагН|егагспу<'ГгРЕ08Т 3(ЯгаувпрегМопагаг, ЯИ(умопа(ег, ЗИ|уво(С|ег), Орневрас!огуцпИ,АЬа!гас(Епепугасгогу> Еааугаге)Епетугасгогу Рис. 9.3. Уувраркил классов, гекерируемал длл класса Еа~Еевв!Епвтууасгогу 9.4. Реализация шаблона АЬв1и ас1 Рас1огу на основе прототипов Шаблон проектирования Ргототуре ((лагпгпа с! а1,, 1995) описывает метод создания объектов, начиная с прототипа, представляющего собой некий архетип.
Новые объекты певуче!от<я путем клонирования прототипа. Суть этого способа заключается в тол|, что функция клонирования является виртуальной. В главе 8 детально обсуждалась важная проблема, возникающая при создании полиморфных объектов, Эта проблема называется дилеммой вирагуальиого коиструктора: при создании объекта с нуля нужно знать тип создаваемого объекта, хотя полиморфизм предполагает отсутствие точной информапии о типе. 248 Часть П. Компоненты Шаблон проектирования егохосуре избегает этой лилеммы, используя прототип объекта. Имея прототип, мы можем использовать преимушества виртуальных функций.
Дилемма виртуального конструктора по отношению к самому прототипу остается нерешенной, но теперь она носит намного более локальный характер. В описанном выше примере подход, основанный на применении прототипов при создании врагов, задействованных в компьютерной игре, вынуждает использовать указатели на объекты базовых классов 5о1Н ег, мопзтег и 5црегмопзтег. В этом случае код может выглядеть примерно так) с1ава баюелрр чозд 5е1естсече1 О ( )т (пользователь выбрал уровень повышенной сложности) ( ргото5о1Нег .гезет(пеы вадбо1Нег); рготомопзтег .гезет(пеы вадмопзтег); ргосовмрегмопзтег .гезет(пеы ваовцрегмопзсег); ) е1зе ( рГОСово1Нег . гезет(пеы 5(11уво1Нег); ргогомопзтег .
гезес(пеы 5з11умозпгег) ," ргоховмрегмопзСег . гезеС(пеы 5(11убмрегмозптег); ) 5о1Нег* майезо1НегО ( // в каждом вражеском классе определяется // виртуальная функция с1опе гетцгп регосо5о1Нег ->с1опеО; ) классы макемопзсег и махе5мрегмопзсег определяются аналогично ргзчате: // используем эти прототипы для создания врагов амто ртг<5о1Нег> рготово1Нег амто рсг<мопзсег> ргосомопзхег ацго рсг<5орегмопзтег> ргосовцрегмопзтег ; Разумеется, в реальном коде лучше было бы разделить интерфейс и реализацию. Основная идея заключается в том, чтобы класс саюелрр хранил указатели на базовые вражеские классы — прототипы.
Класс баюелрр использует эти прототипы для создания вражеских объектов, применяя к ним виртуальную функцию с1опе. Реализация шаблона АЬзтгасс еастогу, основанная на применении прототипов, может хранить указатель на каждый тип изделия и использовать для создания новых изделий Функцию С1опе. В классе сопсгесеяассогу, использующем прототип, больше нет необходимости предусматривать конкретные типы. В нашем примере для созлания объектов классов 5111у5о)Нег и вабво1Нег нужно лишь передать фабрике соответствуюшие прототипы, Статический тип прототипа — базовый класс 5о1Нег.
Фабрика не обязана ни- з Упит<, этот коа неверно обрабатывает исключитсльныс ситуации. Исправьте его самостоятельно. 249 Глава 9. Шаблон АЬвзгас1 Расзогу чего знать о конкретных типах обьектов. Она просто вызывает виртуальную функ- цию-член с1опе из соответствующего прототипа.
Это ослабляет зависимость конкрет- ных фабрик от конкретных типов. Однако, для того чтобы механизм класса пепь1пеагн1егагсйу работал правильно, нужен список типов. Напомним, как выглядит объявление класса сопс гетегастогу. тевр1ате < с1азз лбзтгастгаст, тевр1ате <с1азз, с1азз> с1ааа сгеатог, с1азз ть1зт > с1азз сопсгетегастогу; Класс ть1зт — это список конкретных излелий.