Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 80
Текст из файла (страница 80)
В частности, в большинстве реализаций С++ изменение размера базового класса треоует перекомпиляции всех производных классов. Наконец, нашей программе, возможно, придется работать в смешанном окружении, где сосуществуют различные системы пользовательского интерфейса. Это может произойти в результате того, что две системы каким-то образом совместно используют экран или потому, что наша программа должна обмениваться информацией с пользователями других систем.
Жесткое встраивание какой-то системы пользовательского интерфейса как одного и единственного базового класса для нашего одного и единственного интерфейса /эа! Ьох не достаточно гибко для решения таких задач. 12.4. Проектирование иерархий классов 12.4.2. Абстрактные классы Давайте начнем сначала и построим новую иерархию классов, которая решит выявленные проблемы, присущие традиционному подходу: [1) Система пользовательского интерфейса должна быть деталью реализации, скрытой от пользователей, которые не хотят о ней знать.
[2( Класс уоа! Ьох не должен содержать данных. [3[ После изменений в системе пользовательского интерфейса не должно происходить перекомпиляции классов семейства!ва! Ьох. [4[ Несколько !ва! Ьох для различных систем пользовательского интерфейса должны иметь возможность сосуществовать в нашей программе. Для постижения втой цели существует несколько альтернативных подходов. Здесь я представлю один пз них, который непосредственно «отображаетсяв на С++. Во-первых, я определю класс.Ьа! Ьох как чистый интерфейс: с1аее1оа! Ьох( риЫ!с: Ыг!иа! т! Ое! иа!ие () = 0; иг!иа(ио(дзе! иа!ие(тс!) О; гнгсиа! иои! гезе! иа1ие (т! !) = 0; и(г(иа! оо!6 рготр! () = О; т ггиа1 Ьоо! и аз сБапаес( () сопя! = О; о!г(иа! -Ба! Ьох () () Это намного понятнее, чем исходное объявление упа! Ьох. Данные исчезли вместе с упрощенными реализациями функций-членов.
Также исчез конструктор, потому что нет данных, которые нужно инициализировать. Вместо зтого я добавил виртуальный деструктор, чтобы гарантировать правильную очистку данных, которая будет определена в производных классах. Определение !оа! е1(с(ег может выглядеть следующим образом: с!азз 1оа! з(!бег, риЫ!с !иа! Ьох, рго!ес!ес( ВВт!пдоги ( риЬВс 1оа! з!!Вег (!пг, !и!)) -!иа! з!и!ег (), !и!д«Ч оа!ие ()) эоыяег пп!ие ((и(!); рго!ес!ед.
1)функции, замедляющие виртуальные () функции ВВт(пг!от, например, )1 ВВинп дат пугаю(), ВВт(пдотптоизе ! Ы! () рг!оа!е: !/данные, необходимые !па! з!(бег ); Производный класс !оа! в1!!(ег является наследником абстрактного класса (!оа1 Ьох), который требует, чтобы !оа! е1!с(ег реализовал чисто виртуальные функ- 368 Глава 12. Производные классы ции базового класса. 1иа1 яЕЫег также наследует от ВВш!псЕош, который предостапляет необходимые средства для реализации этих функций.
Так как 1иа! Ьох предоставляет интерфейс для производного класса, наследование сделано открытым. ВВси!пЫош является только средством реализации, поэтому его наследование объявлено защищенным (ч 15.3.2). Их этого следует, что программист, пользующийся!иа1 ЫЫег, не может непосредственно использовать средства, определенные в ВВш!пдош. Интерфейс Еиа1 я!!аег состоит из интерфейса, унаследованного от 1иа1 Ьох, плюс то, что явно объявлено в 1иа1 яЫег. Я использовал защищенное наслелование вместо более ограничительного (и обычно более безопасного) закрытого, для того чтобы ВВги!псЕош был доступен для классов, производных от Еиа1 ЫЫег, Непосредственное наслелование более чем от одного класса обычно называют множественным наследованием (э" 15.2).
Обратите внимание, что в Еиа1 я!Ыег должны быть замещены функции и из Еиа! Ьох, и из ВВш!пгЕош Поэтому, прямо или косвенно, он должен быть производным от обоих классов. Как показано в 8 (2ААА, возможно косвенное наследование 1иа1 яЫег от ВВтпсЕош путем объявления ВВш!пс!ош базовым классом для Еиа( Ьох, но это привело бы к нежелательным побочным эффектам.
Аналогично, объявление екласса реализации» ВВш!пс(ош членом !иа! Ьох не является решением, потому что класс не может заместить виртуальныс функции своих членов (э 24.ЗА). Представление окна (зн(проч ) в виде члена ВВш!пдош" в 1иа1 Ьох ведет к совершенно другому стилю проектированию Ц !2.7(14(, з 25.7). Интересно, что приведенное объявление 1иа1 яЕЫег позволяет написать код приложения абсолютно также, как и раньше.
Все что мы сделали — по-новому, более логичным образом, структурировали летали реализации. Многие классы требуют некоторой формы очистки до уничтожения объекта. Так как абстрактный класс 1иа1 Ьох не может знать, требуется ли в производном классе такая очистка, он должен предположить, что требуется.
Мы гарантируем необходимую очистку, объявив виртуальный деструктор Еиа1сЬохс-Еиа! Ьох () в базовом классе и замешая его в производных. Например; иоЫЕ(1иа1 Ьох' р( ( 0- с!е!еге р; Оператор с!е1е!е явным образом уничтожает объект, на который указывает р. Нет способа точно определить, к какому классу принадлежит обьект, на который указывает р, но благодаря виртуальному деструктору Еиа1 Ьох будет вызван нужный деструктор.
Иерархию Еиа1 Ьох можно теперь определить следующим образом: с!аяягеа! Ьох(/' ... '/); с1аяя !иа1 ЫЫег: ри6!!с !ие! Ьох, ргогесгеЫ ВВняпдеси ( /' ... '/ ); с!аяя !иа! сЕ!а1: риЫ!с Еда! Ьох, рге!ес!ейВВю!пс(оте(/' ... */(; с!аяяР!аяЫпд Ыа! я!Ыег: риЫЫ!иа! я!Ыег(/* ... '/); с!аяя Рорир !иа! я!Ыег: рибйс !иа! я!Ыег(/',. */); нли графически (нспользуя очевидные сокращения): Зб9 12.4. Проектирование иерархий классов ВВв(пйов 1оа1 Ьох ВВвшйов Рорир Ьоа( в!Ыег Р1авЬ1пд Ьца1 в1Ыег Я пользовался пунктирной линией для отображения защищенного наследования. Если речь идет об обычном пользователе, это становится просто деталью реализации. 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пйов ВВ Ьда1 в1!йег СЮ гоа1 в1!йег Глава 12. Производные классы 370 Как правило, мы поступим еше лучше, если используем более специфические классы в иерархии реюгизации. Например, еслибы в системе от «Крутых Баксов» существовал ползунок, мы определили бы наш 1па! зЫег как производный непосредственно от ВВе1Ыег. с1аязВВ та1 з1Ысг: риЫк1оа! з1(г!егрго1сс!ег!ВВз!(г!ег(/' ...'/), с1акз СЮ та1 з1Ыег: риЫгс1оа1 зЫсг рго!ес!ег!Стгз1!г!ег(/* ... '/), илгл в графическом виде: ВВв1лс1ов 1па1 Ьох С)Рв!лс(ов 1 ВВи11аег 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оа! яЫег(/*...