Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 753
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
1 ... 12 13 14 15 16 17 18 19 ... 104
ГЛАВА 4 Основные решения, которые приходится принимать при конструировании
121
Г Л А В А 6
Классы
Содержание
쐽
6.1. Основы классов: абстрактные типы данных
쐽
6.2. Качественные интерфейсы классов
쐽
6.3. Вопросы проектирования и реализации
쐽
6.4. Разумные причины создания классов
쐽
6.5. Аспекты, специфические для языков
쐽
6.6. Следующий уровень: пакеты классов
Связанные темы
쐽
Проектирование при конструировании: глава 5
쐽
Архитектура ПО: раздел 3.5
쐽
Высококачественные методы: глава 7
쐽
Процесс программирования с псевдокодом: глава 9
쐽
Рефакторинг: глава 24
На заре компьютерной эпохи программисты думали о программировании в тер- минах операторов. В 1970–80-е о программах стали думать в терминах методов.
В XXI веке мы рассматриваем программирование в терминах классов.
Класс — это набор данных и методов, имеющих общую, целостную, хо- рошо определенную сферу ответственности. Данные — необязательный компонент класса: класс может включать только методы, предоставляю- щие целостный набор услуг. Одним из главных условий эффективного програм- мирования является максимизация части программы, которую можно игнориро- вать при работе над конкретными фрагментами кода. Классы — главное средство достижения этой цели.
Эта глава содержит экстракт советов по созданию высококачественных классов.
Если вы только знакомитесь с концепциями объектно-ориентированного програм- мирования, она может показаться слишком сложной. Если вы не прочитали главу
5, вернитесь к ней. Затем начните с раздела 6.1, который поможет понять осталь- ные разделы главы. Если вы уже знакомы с основами классов, можете, просмот- рев раздел 6.1, начать серьезное чтение с раздела 6.2, в котором обсуждаются http://cc2e.com/0665
122
ЧАСТЬ II Высококачественный код интерфейсы классов. В разделе «Дополнительные ресурсы» в конце главы вы най- дете список вводных материалов по данной теме, книг более высокого уровня и ресурсов, специфических для языков программирования.
6.1. Основы классов: абстрактные типы данных
Абстрактный тип данных (АТД) — это набор, включающий данные и выполняе- мые над ними операции. Операции описывают данные для остальной части про- граммы и позволяют их изменять. Слово «данные» используется в выражении «аб- страктный тип данных» довольно условно. АТД может быть графическое окно со всеми влияющими на него операциями, файл с файловыми операциями, таблица страховых тарифов с соответствующими операциями и др.
Понимание концепции АТД необходимо для понимания объектно-ориентированного программирования. Не имея ясного представления об АТД, программисты создают клас- сы, которые только называются «классами», будучи на самом деле лишь удобными контейнерами, содержащими наборы плохо согласующихся друг с другом данных и методов. По- нимание АТД облегчает создание классов и их изменение с течением времени.
В книгах по программированию обсуждение АТД традиционно носит математиче- ский характер. Довольно часто можно встретить высказывания вроде: «АТД можно понимать как математическую модель с определенным для нее набором операций».
И создается впечатление, что АТД подойдет разве что в качестве снотворного.
Такие сухие объяснения АТД никуда не годятся. АТД удивительны тем, что позво- ляют работать с сущностями реального мира, а не с низкоуровневыми сущностя- ми реализации. Благодаря этому вместо вставки узла в связный список можно добавить ячейку в электронную таблицу, новый тип окна в список типов окон или очередной пассажирский автомобиль в программу, моделирующую поток движе- ния. Возможность работать в проблемной области, а не в низкоуровневой облас- ти реализации программы очень удобна. Используйте ее!
Пример необходимости АТД
Для начала приведем пример ситуации, в которой применение АТД было бы по- лезным. После этого мы сможем углубиться в подробности.
Допустим, вы пишете программу, управляющую выводом текста на экран с исполь- зованием разнообразных гарнитур шрифтов, их размеров и атрибутов (например,
«полужирный» и «курсив»). За работу со шрифтами отвечает конкретная часть программы. При использовании АТД данные — названия гарнитур, размеры и атрибуты шрифтов — будут объединены в одну группу с обрабатывающими их методами. Набор данных и методов, служащих одной цели, — это и есть АТД.
Без АТД вам пришлось бы принять специализированный подход к работе со шриф- тами. Скажем, для выбора шрифта размером 12 пт, которым могли бы соответ- ствовать 16 пикселов, вы написали бы что-то вроде:
Перекрестная ссылка Размыш- ление в первую очередь об АТД
и только во вторую о классах является примером программи- рования с использованием язы- ка в отличие от программиро- вания на языке (см. разделы 4.3
и 34.4).
ГЛАВА 6 Классы
123
currentFont.size = 16
Создав набор библиотечных методов, код можно было бы сделать чуть понятнее:
currentFont.size = PointsToPixels( 12 )
Кроме того, атрибуту шрифта можно было бы присвоить более определенное имя,
например:
currentFont.sizeInPixels = PointsToPixels( 12 )
Однако при этом вы не смогли бы включить в программу сразу два поля, опреде- ляющих размер шрифта:
currentFont . sizeInPixels (размер шрифта в пикселах) и
currentFont. sizeInPoints (размер шрифта в пунктах), — потому что тогда структура
currentFont не смогла бы узнать, какое из них использовать. Кроме того, изменяя размеры шрифтов в нескольких местах, вы распространили бы похожие строки по всей программе.
Для выбора полужирного начертания вы могли бы написать код, использующий логическое ИЛИ и шестнадцатеричную константу
0x02:
currentFont.attribute = currentFont.attribute or 0x02
Этот код можно немного улучшить, но лучшее, что вы получите, используя спе- циализированный подход, будет похоже на:
currentFont.attribute = currentFont.attribute or BOLD
или на что-нибудь такое:
currentFont.bold = True
Как и в случае с размером шрифта, проблема здесь в том, что клиентский код должен контролировать элементы данных непосредственно, а это ограничивает число возможных способов применения структуры
currentFont.
Такой подход к программированию способствует распространению похожих строк кода по всей программе.
Преимущества использования АТД
Проблема не в том, что специализированный подход — плохая методика програм- мирования. Просто вы можете заменить его на лучшую методику, преимущества которой описаны ниже.
Возможность сокрытия деталей реализации Сокрытие информации о ти- пах данных шрифта подразумевает, что при необходимости изменения типа дан- ных вы сможете изменить его в одном месте, не влияя на всю программу. Напри- мер, если вы не скроете детали реализации в АТД, то при изменении одного вида представления полужирного шрифта на другой вам придется изменить каждый фрагмент кода, в котором задается полужирное начертание. Сокрытие информа- ции защитит остальную часть программы и в тех случаях, если вы решите хра- нить данные во внешнем хранилище, а не в памяти или переписать все методы,
выполняющие операции над шрифтами, на другом языке.
124
ЧАСТЬ II Высококачественный код
Ограничение области изменений Если вы захотите разнообразить шрифты и реализовать для них дополнительные операции (такие как переключение на над- строчный шрифт, перечеркивание и т. д.), вы сможете изменить один фрагмент кода, и это не повлияет на остальную часть программы.
Более высокая информативность интерфейса Код currentFont . size = 16 не- однозначен, так как число
16 может определять размер шрифта и в пикселах, и в пунктах. Контекст об этом ничего не говорит. Объединение всех похожих опера- ций в АТД позволяет определить весь интерфейс в терминах пунктов, в терминах пикселов или четко разделить оба варианта, помогая избежать путаницы.
Легкость оптимизации кода Для повышения быстродействия операций над шрифтами вы сможете переписать несколько четко определенных методов, а не блуждать по всей программе.
Легкость проверки кода Нудную проверку правильности команд вида cur-
rentFont . attribute = currentFont . attribute or 0x02 вы сможете заменить более про- стой проверкой правильности вызовов
currentFont.SetBoldOn(). В первом случае мож- но указать неверное имя структуры, неверное имя поля, неверную операцию (
and
вместо
or) или неверное значение атрибута (0x20 вместо 0x02). В случае вызова
currentFont.SetBoldOn() ошибкой может быть лишь указание неверного имени мето- да, так что заметить ее легче.
Удобочитаемость и понятность кода Команду вида currentFont . attribute or
0x02 можно улучшить, заменив 0x02 на BOLD (или что там представляет константа
0x02), но даже после этого по удобочитаемости она не сравнится с вызовом ме- тода
currentFont . SetBoldOn().
Вудфилд, Дансмор и Шен провели исследование, участники которого —
аспиранты и студенты старших курсов факультета информатики — дол- жны были ответить на вопросы о двух программах: одна была разделена на восемь методов в функциональном стиле, а вторая — на восемь методов АТД
(Woodfield, Dunsmore, and Shen, 1981). Студенты, отвечавшие на вопросы о вто- рой программе, получили на 30% более высокие оценки.
Ограничение области использования данных В только что представленных примерах структуру
currentFont нужно изменять непосредственно или передавать в каждый метод, работающий со шрифтами. При использовании АТД вам не при- шлось бы ни передавать ее в методы, ни превращать в глобальные данные. АТД
просто включал бы структуру, содержащую данные
currentFont. Прямой доступ к этим данным имели бы лишь методы из состава АТД, но не какие бы то ни было другие методы.
Возможность работы с сущностями реального мира, а не с низкоуровне-
выми деталями реализации АТД позволяет определить операции над шриф- тами так, что большая часть программы будет сформулирована исключительно в терминах шрифтов, а не доступа к массивам, определений структур или значе- ний
True и False.
В нашем случае в АТД можно было бы включить методы:
currentFont.SetSizeInPoints( sizeInPoints )
currentFont.SetSizeInPixels( sizeInPixels )
ГЛАВА 6 Классы
125
currentFont.SetBoldOn()
currentFont.SetBoldOff()
currentFont.SetItalicOn()
currentFont.SetItalicOff()
currentFont.SetTypeFace( faceName )
Эти методы, вероятно, были бы короткими — пожалуй, они напоминали бы код, приведенный при обсуждении специализированного подхода к управлению шрифтами. Различие двух подходов в том, что, используя АТД,
вы изолируете операции над шрифтами в наборе методов, который предоставляет остальным частям программы, работающим с шрифтами, улучшенный уровень аб- стракции и защищает остальной код от изменений операций над шрифтами.
Другие примеры АТД
Допустим, вы разрабатываете приложение, управляющее системой охлаждения ядерного реактора. С системой охлаждения можно работать как с АТД, опреде- лив для нее такие операции:
coolingSystem.GetTemperature()
coolingSystem.SetCirculationRate( rate )
coolingSystem.OpenValve( valveNumber )
coolingSystem.CloseValve( valveNumber )
Конкретная реализация данных операций зависела бы от конкретной среды. Ос- тальные фрагменты программы взаимодействовали бы с системой охлаждения при помощи этих методов и могли бы не беспокоиться о внутренних деталях реали- зации структур данных, их ограничениях, изменениях и т. д.
Вот дополнительные примеры абстрактных типов данных и операций, которые можно было бы для них определить:
Система регулирования
Кофемолка
Топливный бак
скорости
Задать скорость
Включить
Заполнить бак
Получить текущие параметры
Выключить
Слить топливо
Восстановить предыдущее
Задать скорость
Получить емкость топ- значение скорости ливного бака
Отключить систему
Начать перемалывание
Получить статус топлив кофе ного бака
Прекратить перемалывание кофе
Список
Стек
Инициализировать список
Фонарь
Инициализировать стек
Вставить элемент
Включить
Поместить элемент в стек
Удалить элемент
Выключить
Извлечь элемент из стека
Прочитать следующий элемент
Прочитать верхний эле- мент стека
(
см. след. стр.)
126
ЧАСТЬ II Высококачественный код
Система справочной
Меню
Файл
информации
Добавить раздел
Создать новое меню
Открыть файл
Удалить раздел
Уничтожить меню
Прочитать файл
Задать текущий раздел
Добавить в меню
Записать файл новый элемент
Отобразить окно
Удалить элемент меню
Установить указатель справочной системы файла
Уничтожить окно
Активировать элемент
Закрыть файл справочной системы меню
Отобразить указатель
Деактивировать элемент информационных разделов меню
Вернуться к предыдущему
Отобразить меню
Лифт
разделу
Скрыть меню
Переместиться на один этаж вверх
Указатель
Получить индекс
Переместиться на один выбранного элемента этаж вниз меню
Выделить блок памяти
Переместиться на кон- кретный этаж
Освободить блок памяти
Сообщить текущий номер этажа
Изменить объем
Вернуться на первый этаж выделенной памяти
Изучение этих примеров позволяет вывести принципы использования АТД, ко- торые мы и обсудим.
Представляйте в форме АТД распространенные низкоуровневые типы
данных Обычно при обсуждении АТД речь идет о представлении в форме АТД
популярных низкоуровневых типов данных. Как вы могли заметить по примерам,
в форме АТД можно представить стек, список, очередь и почти любой другой по- пулярный тип данных.
Спросите себя: «Что представляет этот стек, список или эта очередь?» Если стек представляет набор сотрудников, рассматривайте АТД как набор сотрудников, а не как стек. Если список соответствует набору счетов, рассматривайте его как набор счетов. Если очередь представляет ячейки электронной таблицы, обращайтесь с ней как с набором ячеек, а не обобщенных элементов. Используйте как можно более высокий уровень абстракции.
Представляйте в форме АТД часто используемые объекты, такие как
файлы Большинство языков включает несколько АТД, которые известны всем программистам, но не всегда воспринимаются как АТД. В качестве примера при- веду операции над файлами. При записи данных на диск ОС избавляет вас от за- бот, связанных с позиционированием головки чтения/записи, выделением новых секторов на диске при заполнении старых и интерпретацией непонятных кодов
ГЛАВА 6 Классы
127
ошибок. ОС предоставляет первый уровень абстракции и соответствующие ему
АТД. Высокоуровневые языки предоставляют второй уровень абстракции и АТД
для этого уровня. Высокоуровневые языки скрывают от вас детали генерации вызовов ОС и работы с буферами данных. Они позволяют рассматривать область диска как «файл».
АТД можно разделить на уровни аналогичным образом. Хотите использовать АТД
на уровне операций со структурами данных (таких как помещение элементов в стек и их извлечение) — прекрасно, но поверх него можно создать и другой уро- вень, соответствующий проблеме реального мира.
Представляйте в форме АТД даже простые элементы Для оправдания ис- пользования АТД не обязательно иметь гигантский тип данных. Одним из АТД в нашем списке примеров был фонарь, поддерживающий только две операции:
включение и выключение. Вам может показаться, что создавать для операций «вклю- чить» и «выключить» отдельные методы слишком расточительно, однако на самом деле АТД выгодно использовать даже в случае самых простых операций. Представ- ление фонаря и его операций в форме АТД облегчает понимание и изменение кода,
ограничивает потенциальные следствия изменений методов
TurnLightOn() и Turn-
LightOff() и снижает число элементов данных, которые нужно передавать в методы.
Обращайтесь к АТД так, чтобы это не зависело от среды, используемой
для его хранения Допустим, ваша таблица страховых тарифов настолько вели- ка, что ее нужно всегда хранить на диске. Вы могли бы представить ее как «
файл
тарифов (rate
file)» и создать такие методы доступа, как RateFile . Read(). Однако,
ссылаясь на таблицу как на файл, вы сообщаете о ней больше информации, чем следовало бы. Если вы когда-нибудь измените программу так, чтобы таблица хра- нилась в памяти, а не на диске, код, обращающийся к ней как к файлу, станет не- корректным и начнет вызывать замешательство. Поэтому старайтесь присваивать классам и методам доступа имена, не зависящие от способа хранения данных, и обращайтесь не к конкретным сущностям, а к АТД, таким как таблица страховых тарифов. В нашем случае класс и метод доступа следовало бы назвать
rateTable.Re-
ad() или просто rates. Read().
Работа с несколькими экземплярами данных
при использовании АТД в средах, не являющихся
объектно-ориентированными
Объектно-ориентированные языки автоматически поддерживают работу с несколь- кими экземплярами АТД. Если вы использовали исключительно объектно-ориен- тированные среды и вам не приходилось реализовывать поддержку работы с не- сколькими экземплярами данных, можете положиться на свою удачу! (И перейти к следующему разделу — «АТД и классы»).
Если вы программируете на C или другом языке, не являющемся объектно-ори- ентированным, поддержку работы с несколькими экземплярами данных нужно реализовать вручную. В целом это значит, что вы должны создать для АТД серви- сы создания и уничтожения экземпляров данных и спроектировать другие сер- висы АТД так, чтобы они могли работать с несколькими экземплярами.