Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 80
Текст из файла (страница 80)
Чтобы упростить изложение основных идей проектирования, я сознательно не рассматриваю вопрос о том, как именно программа ожидает ввод. Возможно программа действительно ожидает ввода в функции дег ча1ие (), а может она ассоциирует 1ча1 Ьох с некоторым событием и готовится реагировать на него функцией обратного вызова (саПЬас)( бзпс((оп); также возможно, что она запускает код 1ча1 Ьох в отдельном потоке, а потом опрашивает его состояние. Конечно, такие вопросы предельно важны в разработках конкретных систем пользовательского интерфейса. Однако даже минимальное обсуждение этих вопросов отвлекло бы нас от изучения средств языка и общих методов программирования, которые совсем не ограничиваются одними лишь пользовательскими интерфейсами.
Они применимы к очень широкому кругу задач. Конкретные элементы ввода определяются как производные от 1ча1 Ьох классы. Например: 12.4. Проектирование иерархий классов Э89 С!авз 1ча! Ьох: риЫ1с ВВюгп«1ою( /* ... */ ); с!иве 1ча1 в1Ыег: риЬВс 1ча! Ьох( /* ... *l 1; с!аев1ча1 «1!ай: риЫЫ 1ча1 Ьох( !* ... */ ); с1аев Р1авЫпя Ыа1 в1Ыег: риЫЫ 1ча! зРЫег( /* ... *! 1; с(аев Рорир Ыа1 в1Ыег: риЬВс 1ча1 ЫЫег( l* ... *l ); У переписав для использования В с ВВюии(о или в графической форме: ВВпяп«(ою 1ча1 сйа1 1ча1 ЫЫег 1ча1 сда1 12.4.1.1.
Критика Представленный дизайн во многих случаях работает замечательно, а соответствующая классовая иерархия хорошо подходит для решения многих проблем. Тем не менее, отдельные детали все же заставляют нас рассмотреть и лругие альтернативы. Мы сделали ВВю1пс!ою базовым классом для 1ча1 Ьох. Это не совсем верно. Использование ВВю!пз!ою не входит в понимание существа 1ча1 Ьох; это всего лишь деталь реализации.
Однако применение этого класса в качестве базового для 1ча1 Ьох превратило его в важнейший элемент дизайна всей системы. Это в каких-то случаях может быть приемлемым, например, если мы накрепко связываем свои разработки с «В(я Вцсйз 1пс.». Но что, если мы захотим визуализировать наш 1ча! Ьох в рамках графической системы от «1шрепа! Вапапаз», «1.(Ьега(ес( БоГьчаге» или «Совр(!ег%Ь(ггеа»? Нам тогда придется поддерживать четыре различные версии программы: 1ча! Йа!, позволяющий выбрать значение с помощью вращающейся «ручки»; Р!алЫпВ Ыа1 в!Ыег, который мерцает при вызове рготрг(); Рорир Ыа1 л1Ыег, который реагирует на вызов рготр!() «всплытием» на экране где-нибудь в заметном месте, чтобы пользователь не мог его проигнорировать. Но откуда при этом возьмется графическая начинка для элементов ввода? Большинство систем графического интерфейса пользователя предоставляют класс, определяющий основные свойства отображения элементов на экране.
Тогда, если мы,— например, воспользуемся наработками фирмы «В(я Вцс)сз 1пс.», то все наши классы должны наследовать от стороннего класса ВВю)пс(ою. Для этого достаточно сделать класс 1ча1 Ьох производным от ВВю1пВою. Тогда, например, любой элемент ввода может быть «помещен» на экран и при этом сразу будет подчиняться определенным графическим правилам (может изменять свой визуальный размер, может быть отбуксирован с помощью мыши и т.д.), принятым в ВВюте!ою.
Наша классовая иерархия примет при этом следующий вид: Глава 12 Наследование классов 390 В ВВ иегяоп У С)г' гепяоп У 1В гегяоп о' ЕЯ гегяоп с1ая !ги1 Ьих: риЫ1с ВВв(пйов( 1* ... *1 ); с(аиз !иа1 Ьих: риЫЬс С!!'вгпг(ов( 1* ... */ ); с!ахг !иа! Ьох: риЬВс !Ввгп4ов( 1* ...
*l ); с(ия Ба( Ьох: риЫЬс 2Явгпйов( 1* ... */ ); 12.4.2. Абстрактные классы Итак, начнем сначала и построим новую иерархию классов, которая решает проблемы, вытекающие из представленной выше критики традиционной иерархии: 1. Система пользовательского интерфейса должна быть деталью реализации, скрытой от пользователей, не желающих знать о ней. 2. Класс,Ба! Ьох не должен содержать данных. 3. Изменения в системе пользовательского интерфейса не должны требовать перекомпиляции кода, использующего классы иерархии Ба! Ьох.
4. Различные варианты Ба! Ьох для разных систем пользовательского интерфейса должны иметь возможность сосуществовать в нашей программе. Множество версий программы может стать настоящим кошмаром. Другая проблема состоит в трм, что каждый производный класс разделяет данные, объявленные в Ба1 Ьих. Эти данные безусловно являются деталью реализации, вкравшейся в интерфейс Ба1 Ьих. С практической же точки зрения, во многих случаях эти данные неадекватны.
Например, элемент Ба1 и!Ыег не нуждается в специальном хранении связанного с ним значения — его можно легко вычислить по положению ползунка этого элемента во время вызова лег иа1ие() . С другой стороны, хранить связанные, но различные наборы данных — это значит нарываться на неприятности: рано или поздно они окажутся рассогласованными. Кроме того, многие новички склонны объявлять данные защищенными, что усложняет сопровождение. Данные лучше объявлять закрытыми, чтобы разработчики производных классов не пытались запутывать их в один клубок со своими данными. А еше лучше объявлять данные в производных классах, где их можно определить наиболее точным образом, и они не будут усложнять жизнь разработчикам иных (несвязанных) производных классов. Почти всегда интерфейс должен содержать только функции, типы и константы.
Преимущество наследования от ВВв!и!!ов состоит в том, что его возможности сразу становятся доступными пользователям 1иа! Ьох. К сожалению, это же означает, что в случае изменений в ВВв1пВов пользователю придется перекомпилироваться (или даже внести изменения в исходный код), чтобы справиться с последствиями этих изменений. В частности, для большинства реализаций С++ изменения в размере базового класса автоматически влекут за собой необходимость перекомпиляции всех производных классов. И наконец, наша программа может работать в смешанных средах, где сосуществуют различные системы пользовательского интерфейса.
Причина может заключаться в том, что две системы каким-то образом совместно используют экран, или что нашей программе потребовалось взаимодействовать с пользователями других систем. Жесткое встраивание какого-либо пользовательского интерфейса в основу классовой иерархии для нашего единственного интерфейса Ба1 Ьох не является гибким решением в таких ситуациях. 12,4. Проектирование иерархий классов Для достижения поставленных целей сушествует несколько альтернативных подходов. Здесь я представляю один из них, наилучшим образом вписываюшийся в возможности С++. Сначала я определяю Ьа1 Ьох как чистый интерфейс: сыт Ьа! Ьох ( риЫ<с: ч!г<иа! т< Ое< чЫие ( ) = О; ч!г<иа! чоЫ ве< ча!ие ((и«) = О< Ыг<иа! юЫ гезе< ча!ие(т«') = О; зчг<иа! чоЫ рготр<() = Оз ч!заиа! Ьоо! абаз саапяе<<() сопя< = Оз ч!г<иа<-Ьа! Ьох() ( ) Это намного понятнее, чем прежнее объявление 1ча! Ьох.
Данные исчезли вместе с упрошенными реализациями функций-членов. Ушел также и конструктор, так как отсутствуют данные, подлежащие инициализации. Вместо этого я добавил виртуальный деструктор для того, чтобы гарантировать правильную очистку данных, определяемых в производных классах. Определение 1ча! х1Ыег может выглядеть следующим образом: с!аее 1ча! вИег: риЫ(с 1ча! Ьох, рго<ес<е«ВВтпозч ( риЫ<с: Ьа! ЫЫег(<п«,п<) < -Ьа! ЫЫег() з т<ее< ча!ие () з юЫ зе< ча!ие (<п« ) < << ... рго<ес<е<Ь В функции, замещающие виртуальные функции, В например, ВВн<я«онсхугазчО, ВВннп<<он з.'тоизе!Ь(<() р<тиие: В данные для зЫег )< Класс Ьа1 х1Ыег наследуется от абстрактного класса (1ча1 Ьох), требуюшего реализовать его чисто виртуальные функции. Кроме того, 1ча1 х1Ыег наследуется от ВВ<чт<1озч, предоставляюшего необходимые для реализации средства.
Поскольку 1ча! Ьох объявляет интерфейс для производных классов, наследование от него выполняется в открытом режиме (с применением ключевого слова риЫ<с). А поскольку ВВ)чт<!о<ч есть просто средство реализации, то наследование от него выполняется в зашишенном режиме (с использованием ключевого слова рго(ес(е(( — см. 515.3.2). Из этого следует, что программист, используюший 1ча! яВОег не может напрямую воспользоваться средствами ВВн!п<!он, Интерфейс 1ча1 хВ<1ег состоит из открыто унаследованного интерфейса 1ча1 Ьох плюс то, что явно объявляет сам Ьа1 ЫЫег. Я использовал зашишенное наследование вместо более ограничительного (и обычно более безопасного) закрытого, чтобы сделать ВВнпп<(о<чдоступным для классов, производных от 1ча! зЬЫек 392 Глава 12.
Наследование классов Непосредственное наследование от более, чем одного класса, называется мноксесп)пенным наследованием (гпи!пр!е тйепгапсе) (915.2). Отметим, что 1га! кВйегдолжен замешать виртуальные функции из .Ба1 Ьох и ВВвйи1ов. Поэтому он прямо или косвенно должен наследовать от обоих этих классов. Как показано в 9! 2.4.1.1, возможно косвенное наследование от ВВв)пйов, когда последний служит базовым классом для Ба! Ьох, но это имеет свои нежелательные побочные эффекты. Объявление же поля с типом ВВвйм!ов членом класса Ба! Ьок не подходит потому, что класс не замещает виртуальные функции своих членов (924.3.4). Представление графических средств (окна) в виде члена Ба1 Ьок с типом ВВи!лйои* приводит к совершенно другому стилю проектирования со своими собственными компромиссными «за» и «против» (912.7[14], 925.7).
Интересно, что новое объявление 1га! вВйег позволяет написать код приложения абсолютно так же, как раньше. Все, что мы сделали, это реструктурировали детали реализации более логичным образом. Многие классы требуют некоторой формы очнспси данных перед уничтожением объекта. Поскольку абстрактный класс Ьа! Ьох не может знать, требуется ли очистка в производных классах, то ему лучше заранее предположить, что требуется. Мы гарантируем надлежащую очистку, объявляя виртуальный деструктор Ба1 Ьох:: -Ь а! Ьох () в базовом классе, и замешая его в производных классах. Например; го!й!'(1га! Ьох* р) ( У... йе!е! р; ) Операция (1е1е!е явным образом уничтожает объект, адресуемый указателем р.
Мы не можем знать, какому точно классу принадлежит объект, адресуемый указателем р, но благодаря виртуальному деструктору из Ба1 Ьох надлежаший деструктор (опционально) будет вызван. Теперь иерархию Ьа1 Ьох можно определить следуюшим образом: с!аев Ьа! Ьох 1/* ... *l ); с!аев 1га! едйег: риЫ(с 1га! Ьох, ргогесгей ВВвшйов 1 !* ... */ 1; с!аев Ба! й!а1: риЫ!с 1га! Ьох, ргогесгей ВВмппйов ( /* ...