Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 176
Текст из файла (страница 176)
Класс даже с единственной виртуальной функцией потенциально действует как интерфейс к еще нс опрсделспному классу, а в иртуальныефункцииподразумеваютзависимостьотещенееделенных классов Ц 24 3 2.1). Учтите, что минимализм требует от проектировщика больше работы, а не меныпе. Выбирая операции, важно сосредоточится на том, что нужно делать, а не на том, как зто делать. То есть, мы должны сосредоточиться на желаемом поведении, а не на проблемах реализации. Иногда полезно классифицировать операции над классом в соответствии с их использованием внутреннего сосгояния объектов; ° основные операции: конструкторы, деструкторы н копирование; инспекторы: операции, не изменяющие состояния объекта; модификаторы: операции, изменяющие состояние объекта; ° преобразования: операции, производящие объект другого типа, основываясь на значении (состоянии) объекта, к которому они прилагаются; ° итераторы: операции, служащис для доступа к хранящимся в контейнере объектам или для их использования.
Эти категории не ортогональны, Например, итератор можно спроектировать так, чтобы оп был нли инспектором, или модификатором. Эти категории являются просто классификацией, помогающей людям приблизиться к проектированию интерфейса классов. Естественно, возможны другие классификации. Опи особенно полезны для поддержания совместимости набора классов в компоненте. С++ обеспечивает поддержку различения инспекторов и модификаторов в форме функций-членов сопят и яе-сопхб Лналогично, прямо поддерживаются понятия конструкторов, деструкторов, операций копирования и преобразования, 777 23.4. Процесс разработки 23.4.3.3.
Этап 3: определение взаимозависимостей уточните классы, определив кг взаимозивисимогти. различные взаимозависимости обсуждаются в ~ 24.3. Ключевые зависимое~и для рассмо~рения в контексте проектирования — это параметрнзация, наследование и отношение использовиния. Каждая из них требует рассмотрения того, что она означает для класса, отвечающего за одно из свойств системы. Отвечать определенно не значит, что класс должен содер>кать собглвенно все данные, или что его функции-члены должны непосредственно выполнять все необходимые операции.
Напротив, каждый класс с единственной областью ответственности гарантирует, что болыпая часть работы осуществляется прямыми запросами «куда-то еще» для обработки каким-то другим классом, отвечающим за частную подаадачу. Однако учтите, что злоупотребление этим приемом может привести к неэффективному и непонятному проекту, размножив классы и объекты до такой степени, что уже никакая работа не будет выполняться без каскада предшествую>цих запросов на предоставление услуг.
Что можно выполнить прямо здесь и сейчас, и должно быть выполнено именно так. Необходимость рассматривать наследование н отношение использования на данном этапе проектирования (а не во время реализации) ведет напрямую от использования классов к представлению концепций. Это также подразумевает, что единицей проекта является компонеьпа Я 23.4.3, ф 24А), а не отдельный класс. Нараметризация — часто ведущая к использованию шаблонов — это способ сделать неявные зависимости явными, побы было можно представить несколько альтернативных решений, не добавляя новых концепций.
Часто существует выбор: оставить что-то зависящим от контекста, представить это как ветвь в дереве наследования или воспользоваться параметром Я 24.4.1). 23.4.3.4. Этап 4: определение интерфейсов Определите интерфейсы. Закрытые (рг1чаге) функции обычно не нужно рассматривать па данной стадии проектирования. Вопросы реализации, которые нужно рассмотреть на данной стадии, связаны с зависимостями, возникшими на этапе 3. Более того: я вывел для себя эмпирическое правило, что если не возможны по крайней мере две различные реализации класса, то с этим классом что-то не так.
То есть это не представление соответствующей концепции, а просто замаскированная реализация. Во многих случаях раздумья о том, доступна ли для класса какая-нибудь форма эволюции, являются хорошим способом ответа на вопрос; «Достаточно ли независим от реализации интерфейс этого класса?» Отметим, что открытые базовые классы и друзья являются частью открытого интерфейса класса; см. также ~ 11.5 и ~ 24А,2. Обеспечение раздельных интерфейсов для наследования н для универсальных клиентов путем раздельного определения защищенных и открытых интерфейсов может быть очень полезным. На данном шаге обдумываются и определяются точные типы аргументов. В идеале нужно максимально возможное число интерфейсов статически типизировать типами уровня прикладной программы; см.
также ~ 24.2.3 и ~ 24.4.2. Определяя интерфейсы, выявляйте классы, где кажется, что операции поддерживают несколько уровней абстракции. Например, некоторые функции-члены класса г11е могут принимать аргументы типа гйе Иезст1р1ог и другие строковые аргументы, Глава 23. Разработка и проектирование 778 означающие имя файла. Операции с Где г1езсг1р1ог действуют на другом уровне абстракции, чем те, что работают с именами файлов, поэтому следует подумать, стоит ли заносить их в один класс.
Может быть, лучше завести два файловых класса: один, поддерживающий поня~не файлового дескриптора, и другой, поддерживающий понятие имени фанла. Как правило, все операции над классом должны поддерживать один и тот же уровень абстракции. Когда это не так, следует подумать о реорганизации класса и родственных ему классов. 23.4.3.5.
Реорганизация иерархии классов На этапах 1 и 3 мы снова проверяем классы и иерархию классов, чтобы выяснить, удовлетворяют ли они нашим требованиям. Как правило, оказывается, что не удовлетворяют, и нам приходится заниматься реорганизацией, чтобы улучшить эту слруктуру и/или спроектировать новую реализацию.
Самые распространенные случаи реорганизации иерархии классов — вычленение общей части двух классов в новый класс и разбиение старого класса на два новых. В обоцх случаях в результате получается три класса: базовый и два производных. Когда производить такую реорганизацию? Каков обычно признак того, что такая реорганизация окажется полезной? К сожалению, на эти вопросы нет простых универсальных ответов.
И в этом нет ничего удивительного, поскольку мы говорим не о незначительных деталях реализации, а об изменении базовой концепции системы. Фундаментальная — и нетривиальная — операция состоит в том, чтобы найти общность между классами и вычленить их общую часть. Точный критерий общности невозможно определить, но он должен отражать общность в концепции системы, а не просто служить удобству реализации. Признаками того, что несколько классов имеют нечто общее, что можно вычленить в общий базовый класс, служат общие модели использования, сходство наборов операций, сходство реализации, и просто то, что этн классы час~о одновременно всплывают па совещаниях по проектированию.
И наоборот, класс может стать хорошим кандидатом для расщсп.лоция на два, если подмножества операций этого класса имеют различные модели использования, реализовшшые по-разному, и если о классе можно устроить две неаависимые дискуссии. Иногда превращение множества схожих классов в шаблон является способом систематического представления необходимых альтернатив 1Э 244.1). Из-за близкого родства меэкду классами и концепциями проблемы с организацией иерархии классов часто всплывают при обсуждении названий классов и использования их имен.
Если имена классов и классификация, порожденная иерархией классов, звучат неуклюже, значит, вероятно, существует возможность улучшить иерархию. Заметьте: я хочу сказать, что два человека гораздо лучше справляются с анализом иерархии классов, чем один. Если у вас не окажется никого, с кем можно обсудить проект, то полезной альтернативой может стать написание методического руководсгва по проекту с использованием имен классов. Одна из самых важных задач проектирования — обеспечить интерфейсы, которые останутся стаоильными при изменении самих классов.
Часто это лучше всего достигается тем, что класс, от которого зависит много других классов и фупкци1п делается абстрактным классом, представляющим собой самые общие операции. Детали лучше всего оставить более специализированным производным классам, от которых непосредственно зависит меньшее количество функций и классов.
Я скажу больше: чем 779 23.4. Процесс разработки больше классов зависит от класса, тем более универсальным должен быть этот класс, и тем меньше деталей он должен предоставлять Существует сильный соблазн — добавлять операции !и данные) в используемый многими класс. Часто это выглядит как способ сделать класс более полезным и менее нуждающимся в изменениях ! в будущем), В результате получается класс с «жирным» интерфейсом Я 24.4.3) и с членами, поддерживающими слабо связапныс между собой функции. Это снова явля ется признаком того, по прп значительных изменениях в одном из других классов, которые этот класс поддерживает, его придется переделывать. Это, в свою очередь, влечет за собой переделку казалось бы независимых пользовательских и производных к.лаосов.