Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 195
Текст из файла (страница 195)
Таким образом, использование истинно локальных переменных резко уменьшается. (3] Неудобство для пользователей. Чтобы получить выгоду от полиморфизма, обеспечиваемого виртуальными функциями, доступ к Ра1е2 должен осуществляться через указатели или ссылки. (4] Относительно слабая инкапсуляция. Виртуальные операции можно замещать, и защищенными данными можно манипулировать из производных классов Я 12.4.1.1). Естественно, эта цена не всегда значительна, н поведение определенного подобным образом класса часто оказывается именно тем, что мы хотели Я 25.3, й 25.4).
Однако для простого конкретного типа, такого как Ра1е2, эта цена, вероятно, достаточно высока и к тому же необязательна. Глава 25. Роли классов Наконец, четко спроектированный конкретный тип часто является идеальным представленцем для более податливых типов. Например; с!аяз Тла1 ед ( риЫлс. лл' открытый интер4ейс, состоящий в основная иэ виртуальных функнлн! рглоа1е: Тза1е л(; 25.3.
Абстрактные типы Нростейщий способ ослабить связь между пользователями класса и его разработчика- ми, а также между созлающим объекты колом и кодом, полъзующимся такими об ьекта- ми, — ввести абстрактный класс, прелставля ющий собой интерфейс ко множеству реа- лизаций общего понятия. Рассмотрпм бесхитростный класс Зе1(множество): 1етр!а1е<с1азя Т> с!аяз Яе1( риЫ1а Ыг1иа1иоЫ 1пзег! (Т") = О, шг!иа! оо!с! гетоие (Т") = 0; ,л,л вставить лл,л удалить о!гтиа!!л11з тетбег(Т') =0; Ыг1иа! TЯгз1 () =0; Ыг1иа! Т' пех1() = 0; тйгтиа1-5е1() () Так определяется интерфейс к множеству со встроенным понятием итерации по элементам.
Типично отсутствие конструктора и наличие виртуального деструктора Я 12.4.2). Возможно несколько реализаций (ь 16.2.1). Например: гетр!а1е<с!аяя Т> с!аяя Е!я! яе1: риЫ!с Бег<Те, рноа1е !1я1<Ть ( 1етр1а1е<с1аяя Т> с!азя Ъесгог зе1: риб!1с Бег<Т>, ргсиаге оесгог<Т ( Абстрактный класс предоставляет общий интерфейс к реализациям. Это означает, что мы можем пользоваться классом Яе1, не зная, какая из реализаций используется.
Например: оо!г!Т(5е1<Р!апе*>й я) (ог (Р!апе'" р = ярлгя1();р, р = з пех1 ()) ( !! иой код Именно этот способ следует использовать при необходимости вписать конкретные типы (в том числе встроенные) в иерархию классов.
См. также ~ 25.10(11, 847 25.3. Абстрактные типы 7лХС зеГ«Р(аае«> хЕ; Ъесгот зе1<Р!аяе*» о ]300Г ооЫ0(] 7"(з(], Х(о], Для конкретных типов от нас бы потребовалось перепроектирование реализапии классов, чтобы выразить общность, и использование шаблона для того, чтобы ее задействовать. Здесь же мы должны спроектировать общий интерфейс (в данном случае — Яе(), но больше никакой общности, кроме необходимой для реализации зтого интерфейса, не требуется. Более того, пользователи класса Яе( не знают объявлений ПМ зе( и )гес(ог зеб позтому пользователи не зависят от зтих объявлений, и их (объявления) не нужно заново компилировать пли как-либо изменять, если были модифицированы АМ зе( или Уес(ог зей или даже если была введена новая реализация Вег — скажелц 7г ее зеб Все зависимости содержатся в функциях, которые явно пользуются классом, производным от Веб В частности, предполагая общепринятое использование заголовочных файлов, программисту, пишущему Г[ЗеЫ], нужно включить только лебл, а не Аи( зег.д или ) ес(ог зебп.
«Реализационный заголовочный файл» нужен только там, где создаются ЕМ зег и Ъес(ог зеб Реализацию можно еще дальше изолировать от действительных классов, введя абстрактный класс, обрабатыва|ощий запросы на создание объектов («фабрику»; з 12.4.4). Отделение интерфейса от реализаций подразумевает отсутствие доступа к операциям, «естественным» для частной реализапни, но не достаточно универсальным, чтобы стать частью интерфейса. Например, поскольку в множестве нет порядка, в интерфейсе Зе( мы не можем поддержать индексацию, даже если нам придется реализовать частньш случай шаблона Яе(, использующий массив. Это подразумевает потери времени выполнения за счет отсутствия ручной оптимизации. Более того, встраивание функций, как правило, становится недостижимым (разве что в локальном контексте, когда компилятор знает реальный тип), и все интересные операции над интерфейсом становятся вызовами ви1луальных функций, Как и в случае с конкретными типами, иногда затраты на абстрактный тип окупаются, иногда нет, Подводя итог, можно сказать, что абстрактные типы предназначены для того, чтобы: [1] определить одно понятиетаким образом, чтобы позволить сосуществовать в программе нескольким его реализациям; [2] обеспечить приел«лемое быстродействие и затраты памяти, благодаря использованию виртуальных функций; [3] минимизировать зависимость каждой реализации от других классов; [4] быть.
попятными сами по себе. Абстрактные типы не лучше, чем конкретные, зто просто другие типы. Для пользователя важно, но п трудно сделать правильный выбор, Разработчик библиотеки может уйти от проблемы выбора, предоставив и те, и другие типы, и тем самым оставив выбор за пользователем. Важно ясно представлять, какому миру принадлежит класс.
848 Глава 25. Роли классов Ограничение общности абстрактного типа в попытках конкурировать с конкретным типом в быстродействии, как правило, заканчивается неудачей. Это ухудшает возможность использования взаимозаменяемых реализаций без значительной перекомпиляции после внесения изменений. Аналогично, попытки обеспечить «обшность» в конкретных типах, также обычно приводят к неудаче. Это снижает эффективность и удобство применения простых классов. Этн два понятия могут сосуществовать — на самом деле они должны сосуществовать, поскольку конкретные типы обеспечивают реализацию абстрактных типов, — но их не следует смешивать.
Абстрактные типы часто не предназначены для создания дальнейших производных классов. Порождение классов чаще всего используется для реализации. Однако из абстрактного класса можно сконструировать новый интерфейс, совпав производный от него расширенный абстрактный класс. Тогда новый абстрактный класс должен в свою очередь быть реализован путем создания производного неабстрактного класса Я 15.2.5), Почему мы не сделали бМ и 1гес1ог производными от Яе1 сразу, ведь тогда удалось бы избежать введения классов'би1 зе1 и 1гес(пг зе1? Иными словами, зачем иметь конкретные типы, когда можно иметь абстрактные? [1~ Эффективность.
Нам нужны конкретные типы, такие как оес1ог и 6зб без затрат, связанных с отделением реализации от интерфейса (что гюдразумевается стилем абстрактных типов). [21 Позглорное использование. Нам нужен механизм вписывания типов (вроле пес1ог или 11з(), спроектированных «где-то в другом месте», в новую библиотеку или прикладную программу, путем введения для пих нового интерфейса (а не переписывая их). [3) Л(ножественяые интерфейсы. Использование единого класса в качестве базового для многих классов приводит к жирным интерфейсам Я 24,4.3). Для класса, созданного для новых задач, часто лучше обеспечить новый интерфейс (как интерфейс Яе1 для пес1ог), а не изменять старый для подгонки под множество целей.
Естественно, эти пункты связаны между собой, Подробно они обсуждались для примера с Иа1 Ьох Я 12А.2, $15.2.5) и в контексте проектирования контейнеров (з 16.2). Использование базового класса Ве1 привело бы к решению с контейнером, являющимся базовым классом, оппраюгцимся на узловые классы Я 25А).
В разделе 8 25.7 описывается итератор, более гибкий в том смысле, что связав его с реализацией, мы можем специфицировать объекты в точке инициализации и изменять их во время выполнения. 25.4. Узловые классы Иерархия классов строится с точки зрения наследования, отличного от разделения интерфейс/реализация, характерного для абстрактных типов. Здесь класс рассматривается как фундамент для строительства. Даже если это абстрактный класс, он обычно имеет некоторое представление и предоставляет некоторые услуги своим произволным классам. В качестве примеров узлового класса можно привести Ро1ддоп (~ 12.3), первоначальный 1еа1 з1Ыег Я 12А.1) и Яа1е1111е (з 15.2).
Как правило, класс в иерархии представляет общее понятие, для которого производные классы могут считаться специализациями. Типичный класс, спроектированный как составная часть иерархии, узловой класс, опирается на услуги базовых клас- 849 25.4. Узловые классы сов, чтобы обеспечить свои собственные услуги. То есть он вызывает функции-члены базового класса.