Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 196
Текст из файла (страница 196)
Типичный узловой класс обеспечивает не просто реализацию интерфейса, описанного его базовым классом (как это делает класс реализации для абстрактного типа). Он также сам лобавляет новые функции, обеспечивая таким образом более широкий интерфейс. Рассмотрим класс Саг из примера с моделированием уличного движения в 9 24.3.2: с!азз Саг рийы УеЫс!е( ри6!!с // раззепуегз — пассажира, зсее — разльер, // юе!ВЫ вЂ” вес,)ие! сараи!у — елисость бака Саг(Ы! развепуегз, Ььзе сагеуогу в!ее, т! гое!дЬ! !и!/с) : Ъейс!е (раззепдегз, ь!яе, изе!ВЫ) /ие! сараспу (/с) ( /' ...
'/ ) //замещение соответствующих виртуольньи функций из Ъе?йс!е: иоЫ!игп (О!гесг!оп~! // !игп — поворот, довс!?оп — направление //. //добавление специфических для овтолсобиля функций: Ыг!иа! иоЫ асЫ /ие! (!и! атоип!), // автольобилю для езды нужно топливо Важные функции являются конструкторами, посредством которых программист описывает основные свойства, существенные для модели, и (виртуальные) функции, которые позволяют имитирующим процедурам манипулировать автомобилем, не зная его точного типа. Класс Сагможно создать и использовать следующим образом: ооЫ изег () ( Саг*р = пет Саг(Я, есопоту, !500, 00); Йг!ое (р, Ьз Ьоте, МИ); // вход во фрагл~ент, имитирующий // уличное движение Узловому классу обычно нужны конструкторы, и часто непростые.
Этим узловой класс. отличается от абстрактных типов, которые редко имеют конструкторы. Операции над классом Саг в своих реализациях будут, как правило, пользоваться операциями из базового класса (геп!с1е. Кроме того пользователь класса Саг опирается на услуги его базовых классов.
Например, (?еЬ!с(е обеспечивает базовые функции, относящиеся к весу и размерам, так что классу Саг не приходится этим заниматься: Ьоо! ВгЫуезсап егозя (сопя! ЪеИс!ей г) ( // Ьпдуе — мост, //сап сгочз — можно переехать? (/ (тах ые!06!<плие!0Ы ()) ге!ига/а!зе; //,, Это позволяет программисту создавать новые классы, такие как Саги Тгисй, из узлового класса )?еЫс1е, описывая н реализуя только то, что должно отличаться от $еЫс1е. Это 650 Глава 25.
Роли классов часто называют «программирован!!ем отличий> или «программированием по расширению>. Подобно многим узловым классам, сам класс Саг является хорошей кандидатурой лля создания дальнейших производных классов. Например, в классе АтЬи!апсе (скорая медицинская помощь) нужны дополнительные данные и операции, связанные с зкстренпостыо службы: с(аяя АтЬи1апсе: риЬИс Саг, риЬИс Етеуепсу ( риЫ1с АтЬи1апсе (); // за»!егценне вирнпуиль них функций ияСаг; оо1И !игп (!»1гес!1оп)! О яамеи(ение виртуальных функций иэ Етегуепсу: // сйяра(сЬ !о — «направить в», 1осаноп — «л!осто» о1«!иа! оо1д Иьяра!сЬ !о (сопя!йосанопЦ; // добавление функций, специфичных длл скорой по»!ощи: Шг!иа1 1п1 рапеп! сарасну (); О количество носилок Подводя итог, можно сказать, что узловой класс; [1] опирается на свои базовые классы как для своей реализации, так и для предоставления услуг своим пользователям; [2] предоставляет более широкий интерфейс (то есть интерфейс с большим числом открытых функппй-членов) по сравнению с базовыми классами; [3] в своем открытом интерфейсе опирается в первую очередь (но не обязательно только) на виртуальные функции; [4] зависит от всех своих (прямых и непрямых) базовых классов; [5] понятен только в контексте своих базовых классов; [6] может служ!лть базовым для дальнейшего порождения классов; [7] может бы ть использован для создания объектов.
Не все узловые классы будут удовлетворять пунктам 1, 2, 6 и 7, но большинство— удовлетворяют. Класс, не удовлетворяющий пункту 6, напоминает конкретный тип, и его можно назвать конкретныл! узловым ю!иссох!. Например, конкретный узловой класс может использоваться л„пя реализации абстрактного класса Я 12.4.2), а переменные такого класса могут располагаться в памяти стати чески и в стеке.
Такие классы иногда называют классами -листьями. Однако любой код, оперируюший с указателями или ссылками на класс с виртуальными функциями, должен учитывать возможность наличия лальнейшнх производных классов (или допустить, без языковой поддержки, что дальнейших производных классов нет).
Класс, не удовлетворяюьций пункту 7, напоминает абстрактный тип, и его можно назвать абстрактным узловыл! классом. Вс!!едствие неудачной традиции многие узловые классы имеют хотя бы несколько зашишенных членов, чтобы предоставить не столь ограниченный интерфейс для производных классов (~ 12.4.1.1).
851 г5.4. Узловые классы Пункт 4 подразумевает, что для компиляции узлового класса программист должен включить объявления всех его прямых и непрямых базовых классов, а также все объявления, от которых в свою очередь зависят последние. Этим узловой класс такъе отличается от абстрактного типа. Пользователь абстрактного типа пе зависит от классов, использующихся для его реализации, и моъет не включать их в текст для компиляции. 25.4.1. Изменение интерфейсов ооЫ ив ее () О огпкрываем фоал в предположении, нпю он содержит фигура (яйаре), Д,г и при крепллел~ яя в ко пест ее потока ввода к этол~у файлу 1о оЬ1" р=уе1 оЬ1'(яя),(1' чтение объектаияпотока (1' (ВЬаре* яр=с(упит(с саягс$Ьаре'> (р)) ( яр — »агам(); у(испольвованиефигури еряе ( О ошибка: в файле встретилась не фигура Функция ияег () работает с фигурами исключительно посредством абстрактного класса 5йаре и поэтому может пользоваться любыми фигурами.
Использование динамического приведения существенно, так как система объектного ввода/вывода может работать со многимп другими видами объектов, и пользователь может по ошибке открыть файл с вполне нормальными объектами, о которых он никогда не слышал. Такая система объектного ввода/вывода предполагает, что каждый записанный или считанный объект относится к классу, производному от1о оЬ1'.
Класс 1о оЬ1, чтобы позволить нам пользоваться динамическим приведением, должен быть полиморфным типом. Например: с(аяя1о оЬ1( риЬ11с: иьъгиа11о оЬ1 с(опе () сопяг= 0; и(сгиа1 -1о оЬ1' () () /Г пол ил орфи ай Важнейшей функцией в системе объектного вводагвгявода является де1 оЬ/(), которая считывает данные из потока 1я1геат и на основанв и зтих данных создает объекты Узловой класс по определению является частью иерархии. Не все классы в иерархии должны предлагать олин и тот же интерфейс. В частности, производный класс может прсдоставить новые функции-члены, а его <братский» класс — совершенно другой набор функций. С точки зрения проектирования можно рассматривать линамическое приведение с(дпагп(с сая1 Я 15.4) как механизм запроса у объекта, обеспечивает ли он данный интерфейс.
Для примера разберем простую систему объектного вводаггвывода, Пользователи хотят читать объекты из потока, определять, относятся ли они к ожидаемому типу, и пользоваться ими. Например: Глава 25. Роли классов 852 класса. Допустим, что данные, представляющие объект в потоке ввода, предваряются строкой, описывающей класс данного объекта. Работа функции уе1 оЬ! (] заключается в чтении этой строки-префикса и вызове функции, способной создать и инициализировать объект соответствующего класса.
Например: гурег(е11о оЬ!' ('Рг) (!з1геатй( Оуказательнафункцию,возвраи!аюи(ую!о о6!" тарле!пну, РР !о тар; // опюбражает строки в функции создания Ьоо! уе1 июгг((!з1геатй !з, згппуй з); // чтение слова из оввз 1о оЬ! уег о6!'Ьз1геатйз) ( з1г!пу з1г; Ьоо! Ь = уе1 июгг((з, з1г), (/ (Ь ==1а!зе) Гдгот 6!о с!азз (); зачтение начального слова в зп' //проблема с форматом вводи/вывода // поиск з!г для получения функции // нет вхождения з1г РР/= !о тар(зЯ; (/ Ц'== О) 16гот !!пупотп с1азз (); ге !ипс/(з'1 Функция тар, которую вызывает !о тар, содержит пары из имен строк и функций, способных сконструировать объекты классов с этим именем. Мы могли бы определить класс Яйаре обычным образом, за исключением того, что необходимо сделать его производным от 1о оЬ1, как того требует функция изег (): с1азз БЬаре: риЫ!с!о о61( Однако интереснее (и для многих случаев более реалистично) было бы воспользоваться уже определенным ЗЬаре Я 2.6.2) без изменении: с1азз 1о с!гс!е: риЬ!!с С!гс!е,риЬ!!с1о оЬ! ( //СЪс!е — окружность риЬ!!а 1о с!ге!е* с!опе () сопя! //используется копирующий конструктор ( ге1игп пею 1о с!гс!е ("16!з); ) 1о с!гс1е (!зсгеагпй); //инициализация из потока ввода з1а1!с1о оЬ! пеиз с!гс!е (!зггеатйз) (ге!игл пет1о сггс1е (з) ) !о тар("1о с!гс!е )=й1о с!гс!еапет пгс!е, Другие фигуры конструируются точно так же // Гпапу!е — треугольник с!азз !о 1папу!е: риЫ!с уг!апу!е, ри6!(с!о оЬ!'( 0- Этот пример в первую очередь показывает, как класс можно вписать в иерархию прн помощи абстрактного класса с меньшей предусмотрительностью чем та, которая бы потребовалась для того, чтобы построить его в виде узлового класса Ц 12А.2, 8 253).
Конструктор 1о игс1е (!з1геатй) инициализирует объект данными из своего аргумента !з1геат. Функция пеги с!гс1е помещается в !о тар, чтобы дать знать системе объектного ввода/вывода о соответствующем классе. Например: 853 25.5. Действия Если построение «подмостков» для объектного ввода/вывода становится утомнтель- цым, нам поможет шаблон; ьетр)а!е<с!авв Т> с)азз1о: риЫге 1 риис 1о оЬ!( риЫса 1о* с!опе () сопя! ( ге!ига печи 1о ("гЫз); ) //заяви(ение!о оЬ1лс(опе!) 1о ((в!геатй); // ини!(иолизаяия из потока ввода в(айс 1о" пего (о (/вггеать, в) ( ге!и гп пет 1о (в); ) //-.
); Теперь мы можем определить 1о с!ге(е: гуре!!е/!о<С!гс!е> 1о с)гс)е; Впрочем, нам по-прежнему нужно явно определить 1о<Ссгс(е>гйо (!з!геат&), так как этой функции нужно знать подробности о С!гс(е. 11(аблон 1о является примером того, как вписать конкретные типы в иерархию классов, введя вспомогательный класс, которой являлся бы узлом в данной иерархии. Этот класс наследует от своего параметра-шаблона, чтобы позволить приведение пз типа 1о об/. К сожалению, это приводит к тому, что 1о нельзя использовать со встроенными типами: // оболонки для конкретного типа // о!иибка; нельзя создать производниа класс //от встроенного типа !уреде/1о<тза!е> 1о Иа!е, гуреде/1о<!пг> 1о т!; С этой проблемой можно справиться, введя отдельный шаблон для встроенных типов, или воспользовавшись классом, представляющим встроенный тип (з 25.10(1)).
Эта простая система объектного ввода/вывода не может делать все на свете, но она почти умещается на однои странице, а ключевые механизмы, использовавшиеся в ией, имеют множество применений. Воооще говоря, эти приемы можно использовать для вызова функций, основываясь на строке, предоставляемой пользователем, и для манипулирования объектами неизвестного типа посредством интерфейсов, установленных при помощи идентификации типа во время выполнения. 25.5. Действия В С++ самый простой и очевидный способ охарактеризовать действие — написать функцию. Однако если действие нужно отложить, передать до выполнения «куда-нибудь», если оно требует собственных данных, должно комбинироваться с другими действиями Я 25 10 [18, 10) ) и т. и., то часто хочется оформить его в виде класса, который мог бы выполнить желаемое действие, а также предоставить другие услуги.
Очевидным примером такого действия является объект-функция, использующийся со стандартными алгоритмами Я 18А), а также манипуляторы, применяемые с потоками гов!геат (з 21А.6). В первом случае собственно действие выполняется применязощимся оператором, а во втором — операторами «и». В случае с Ропп Я 21А.63) и Ма!г!х Я 22А.7) классы-композиторы используются для откладывания действия, пока не булет собрано достаточно информации для его эффективного выполнения. 854 Глава 25. Роли классов Обычная форма класса-действия — зто просто класс, содержащий только олпу виртуальную функцию (обычно она называется как-нибудь вроде «с(о гбь -- «сделай зтоь): у1 деиствис с!аззАсиол ( риЬйс: о!ЕЕиа! слЕдо !Е(!ой =О; пег!ил! -Асйоп () ( ) С таким классом мы можем написать код — скажем, меню — который сможет хранить действия лля последующего выполнения, не используя указатели на функции, наимея информации о вызываемых объектах и даже не зная имен вызываемых операций.