Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 81
Текст из файла (страница 81)
*! ); с!аев Р!авЫпВ лъа! едйег: риЫ!с 1га! е!Ыег ( !* ... *l ); с!аев Рорир Ба! Ййег: риЫЫ1га! сйЫег ( /* ... *! 1; или в графической форме; ВВпплйов ВВп !пйов Ьа! Ьох Ъ~ в' ~ .Л' Ьа1 сйЫег Ьа1 й!а! РтагЫпя Ба! едйег Рори! Ба1 едйег Я использую пунктирную линию для отображения зашишенного наследования. Для обычных пользователей это деталь реализации.
12.4. Проектирование иерархий классов 393 12.4.3. Альтернативные реализации Полученный дизайн яснее, чем традиционный, и поддерживать его легче. К тому же, он не менее эффективный. Но пока что он по-прежнему не решает проблему со многими версиями: с!аее1»а1 Ьох ( /* ... */ ); //соттоп с1аее 1га1 ейаег: риЬйс 1»а! Ьох, рго!есгег! ВВв!пг!ов(/* ... */ ); с(аге 1»а1 яйаег: риЫ!с 1»а1 Ьох, рго!ес!ег! СИ"в(паев(/* ... */ ); // ... У для ВВ й для СИ' И он не позволяет 1га1 з!Ыег для ВВв/пе!ов сосуществовать с 1»а1 яйаег для СИв!паов, даже если сами эти системы пользовательского интерфейса сосуществуют. Очевидным решением проблемы является определение нескольких вариантов Хга1 ейаег с разными именами: с1аяе 1»а! Ьох ( /* ...
*/ ); с(аяя ВВ па1 е1пмег: риЫЫ 1»а! Ьох, рго!ес!ейййв!пйов ( /* ... */ ); с!ага Ог' !»а1 МЫег: риЬйс 1»а! Ьох, ргогес!еа' СИ'в!паев ( /* ... */ ); й... или графически: Вйв!паев 1иа! Ьох СИ'в!адов Ъ~ ВВ Ыа1 ейаег СИ' па1 ЫЫег Чтобы еще более изолировать наши классы от деталей реализации, мы можем ввести абстрактный класс 1»а! з1Ыег, который наследует от 1га1 Ьох, а системно-зависимые элементы наследовать следующим образом: с!аея1»а! Ьох ( /* ... */ ); с!аяя 1»а! е1Ыег: райк 1»а! Ьох ( /* ... */ ); с!азе ВВ !га! ейаег: риЫ!с1»а! я!Ыег, рго!ес!еа* ВВв!паев ( /* ...
*/ ); сгаее СИ' 1га1 е!Ыег: рпЬйс 1»а1 яйаег, реп!ее!ее' СИ'в!пйов ( /* ... */ ); или графически: 1»а1 Ьох ВВи !паев 1 а1 я1Ыег СИ'в(паев ВВ па! я1Ыег СИ' !»а1 ВЫег Как правило, ситуация только улучшается, если системно-зависимые классы также выстроены в иерархию. Например, если у фирму «В)3 Вцс)сз !пс.» имеется класс ползунков (з)(бег с!аи), то мы производим собственный элемент, наследуя от этого ползунка: Глава 12. Наследование классов с(ат ВВ па( яйаег: ривйс 1на( я(Ыег, ргогес!ег(ВВЫЫег ( /* ... */ ); с(ат С(г' и'а! ЫЫег: рий!!с 1на! «ййег, рго(ес!ео( СИ'«1Ыег ( /* ... */ ); й ...
или в графической форме: ВВкоии(ов СИ)о(по(оп з"на( Ьох ВВЫЫег Ба( «ЬЫег СИ«(Ыег Ъ~ .н ЪК ВВ (на! я!Ыег СИ' Ба( ЫЫег Последнее улучшение особо велико там, где наши абстракции классов не слишком отличаются от абстракций программной системы, обеспечивающей графическую реализацию.
Тогда обьем программирования уменьшается и сводится к необходимости установления соответствия между аналогичными концепциями. Наследование же от общих базовых классов, таких как ВВи!прои, производится редко. Окончательная иерархия будет состоять из нашей оригинальной иерархии интерфейсов, ориентированных на наше приложение: с(ат «на( Ьох ( /* ...
*/ ); с(от 1на( з(Ыег: риЫк Ба( Ьох ( /* ... */ ); с(от 1на( г((а(: риЫк1на( Ьох ( /* ... */ ); с(азя Р(а«Ыпа (на( яййег: риЫк Ба( «1Ыег ( /* ... */ ); с(азя Рорир Ба( ИЫег: риьйс 1на( я(Ыег ( /* ... */ ); и иерархии реализации для различных сторонних графических систем: с1ат ВВ Ба( я(Ыег: риЫк Гни( яйг(ег, рго!ес!ео(ВВ«йг(ег ( /* ... */ ); с(аяя ВВ((а«Ыпе Ба( яйаег: риЫк Р(а«Ыие Ыа( я(Ыег, рго!ес!ей Ввпяпо(озн пз!Ь Ьейя апй вл(я!(ея ( /* ... */ ); с!ат ВВ рорир Ба1 «ййег: риЬйс Рорир Ба( з(Ыег, рго(ес!еа' ВВ«!Ыег ( /* ... */ )' с(ат СИ' (на( «1Ыег: риЫ1с1на( з(Ыег, рго!ес!ео( СИ«1Ыег ( /* ...
*/ ); // ... Используя очевидные аббревиатуры, эту иерархию можно представить в следующем графическом виде: 1на( я(Ыег 1на( сйа( ВВ«й А (рз! А Вво!Мог си'!овеег ввфор СИ (рор сп«р вву! 12.4. Проектирование иерархий классов 395 Исходная иерархия классов Ь а! Ьох не изменилась — просто она теперь окружена классами графических реализаций.
12.4.3.1. Критика Дизайн на основе абстрактных классов очень гибок, и к тому же он не менее прост, что и традиционный дизайн на основе общего базового класса, определяющего систему пользовательского интерфейса. В последнем случае класс окон находится в корне дерева. А при первом подходе исходная иерархия классов нашего приложения неизменная и выступает в качестве корневой для классов реализации. С точки зрения работы программы, оба подхода эквивалентны в том смысле, что почти весь код выполняется одинаково. В обоих случаях можно большую часть времени оперировать семейством классов 1га1 Ьох без оглядки на системы пользовательского интерфейса. Например, нам не нужно переписывать 1игегасг() из 912.4.1 при смене системы пользовательского интерфейса.
Ясно, что если изменится открытый интерфейс систем пользовательского интерфейса, то придется переписывать реализацию каждого класса из семейства Ба1 Ьох. Но в иерархии с абстрактными класса почти весь наш собственный код защищен от изменений в реализации систем пользовательского интерфейса и даже не требует перекомпиляции в таких случаях. Это особенно ценно, когда разработчик систем пользовательского интерфейса выпускает все новые и «почти совместимые» версии.
Наконец, пользователи системы с абстрактными классами избавлены от возможности попасть в зависимость от конкретной реализации. Пользователи системы абстрактных классов 1га1 Ьох не могут случайно воспользоваться механизмами конкретных реализаций, так как им доступны лишь средства, явно предоставляемые в иерархии 1га1 Ьох, и ничто не наследуется неявным образом от базового класса, специфичного для конкретных систем реализации. 12.4.4. Локализация создания объектов с1азз Ь'а! телег ( риЫ/с: Иггиа1ла! йа1* ата1 (тб тг( =О; Игги! Рорир Ба! з!Ыег* рорир з(Ыег(!иг, 1иг) =О; //...
// создать ази1 Р создать рорир з(Ысг Для каждого интерфейса из семейства 1га1 Ьох, который знаком пользователю, класс Ьа! ща(сег предоставляет функцию создания объекта. Такой класс называют Большая часть приложения создается на основе интерфейса Ба1 Ьох. Далее, если система интерфейсов эволюционирует, предоставляя новые средства, то большая часть приложения использует Ба1 Ьох, Ьа1 з!Ыег и другие интерфейсы. Однако создание объектов требует обращения к конкретным именам, специфичным для систем пользовательского интерфейса — Сй' Ба1 йа1, ВВ Яазй(ад Ба! з!Ыег и т.д.
Полезно минимизировать число мест в программе, где создаются объекты. Но создание объектов трудно локализовать, если не делать это систематически. Как всегда решение находится с помощью применения косвенных обращений. Зто можно сделать несколькими способами. Самый простой предоставить абстрактный класс с набором операций создания объектов: 396 Глава 12. Наследование классов фабрикой (Гас!осу), а его функции иногда называют (что может только запутать) виртуальными конструкторами (515.6.2).
Теперь мы представим каждую систему пользовательского интерфейса классом, производным от 1ча1 тайег. с(авз ВВ телег: риЫЫ 1ча! тайег //ВВ-версия ( риЫ1с: Рча( еда!* ада! (1пг, 1п!); Рорир (ча1 в(Ыег* рорир в(Ыег(гпг, Ыг); //... с!ат 13 тайег: риЫ!с 1ча1 тайег //1.Я-версия ( риЬВс: 1ча1 й!а!" й!а!(!пг, !п!); Рорир Ыа! з!Ыег* рорир и!Ыег(!пг, ш!) ! // ... ): Каждая функция создает объект с требуемым интерфейсом и нужной реализацией. Например; 1ча! еда1* ВВ тайег: с В/а1 (!п! а, (пг Ь) ( гегигп пеп ВВ Ыа( й!а1(а, Ь); ) 1ча1 с(!а1* ХБ тайег:: ага! (!пг а, ш! Ь) ( ге!игп пев ЕБ ина1 а/а1 (а, Ь); чо!й ивег (1ча( тайег* р!т) ( 1ча1 Ьох* рЬ = р/т->г!!а1 (О, 99) ! // ... // создаем подходяи(ий ийа! ВВ тайег ВВ (тр1; 13 тайег ЕЯ !тр!! У для пользователей ВВ // для пользователей !Я чоЫ Ипчег () ( изег(аВВ 1тр1) ( ивег(а.Ю ипр!); // используем ВВ // используем Е5 Получив указатель на 1ча! тайег, пользователь может создавать объекты, даже не зная, какая система пользовательского интерфейса задействована.
Например: 12.5 Классовые иерархии и абстрактные классы 397 12.5. Классовые иерархии и абстрактные классы Абстрактный класс является интерфейсом. Классовые иерархии служат средством постепенного и последовательного развертывания классов. Естественно, каждый класс формирует интерфейс для своих пользователей, и некоторые абстрактные классы предоставляют определенную полезную функциональность, но тем не менее, главное предназначение абстрактных классов и иерархий состоит в том, чтобы служить «интерфейсами» и «строительными блоками». Классическая иерархия — это иерархия, в которой отдельные классы предоставляют пользователям полезную функциональность и служат строительными блоками лля реализации более специализированных классов.