Стандарты программирования на С++. 101 правило и рекомендация - [82]

Шрифт
Интервал

при добавлении нового типа.

В идеале добавление новых возможностей в программу должно осуществляться добавлением нового кода, а не изменением старого (см. рекомендацию 37). В реальной жизни это не всегда так — зачастую в дополнение к написанию нового кода мы вынуждены вносить изменения в уже имеющийся код. Такие изменения, однако, крайне нежелательны и должны быть минимизированы по двум причинам. Во-первых, изменения могут нарушить имеющуюся функциональность. Во-вторых, они препятствуют масштабируемости при росте системы и добавлении новых возможностей, поскольку количество "узлов поддержки", к которым надо возвращаться и вносить изменения, все время возрастает. Это наблюдение приводит к принципу Открытости-Закрытости, который гласит: любая сущность (например, класс или модуль) должна быть открыта для расширений, но закрыта для изменений (см. [Martin96c] и [Meyer00]).

Каким же образом мы можем написать код, который будет легко расширяем без внесения изменений? Используйте полиморфизм для написания кода в терминах абстракций (см. также рекомендацию 36), после чего при необходимости добавления функциональности это можно будет сделать путем разработки и добавления различных реализаций упомянутых абстракций. Шаблоны и виртуальные функции образуют барьер для зависимостей между кодом, использующим абстракции, и кодом, их реализующим (см. рекомендацию 64).

Конечно, управление зависимостями обусловлено выбором верных абстракций. Если абстракции несовершенны, добавление новой функциональности потребует изменений интерфейса (а не просто добавления новых реализаций интерфейса), которые обычно влекут за собой значительные изменения существующего кода. Но абстракции потому и называются "абстракциями", что предполагается их большая стабильность по сравнению с "деталями", т.е. возможными реализациями абстракций.

Совсем иначе обстоит дело с предельно детализированным кодом, который использует мало абстракций или вовсе обходится без них, работая исключительно с конкретными типами и их отдельными операциями. Добавление новой функциональности в такой код — сущее мучение.

Примеры

Пример. Рисование фигур. Классический пример — рисование различных объектов. Типичный подход в стиле С использует выбор типа. Для этого определяется член-перечисление >id_, который хранит тип каждой фигуры — прямоугольник, окружность и т.д. Рисующий код выполняет необходимые действия в зависимости от выбранного типа:

>class Shape { // ...

> enum { RECTANGLE, TRIANGLE, CIRCLE } id_;


> void Draw() const {

>  switch (id_) { // плохой метод

>  case RECTANGLE:

>   // ... Код для прямоугольника …

>   break;

>  case TRIANGLE:

>   // ... Код для треугольника …

>   break;

>  case CIRCLE:

>   // ... Код для окружности …

>   break;

>  default: // Плохое решение

>   assert(!"при добавлении нового типа надо "

>           "обновить эту конструкцию" );

>   break;

>  }

> }

>};

Такой код сгибается под собственным весом, он хрупок, ненадежен и сложен. В частности, он страдает транзитивной циклической зависимостью, о которой говорилось в рекомендации 22. Ветвь по умолчанию конструкции >switch — четкий симптом синдрома "не знаю, что мне делать с этим типом". И все эти болезненные неприятности полностью исчезают, стоит только вспомнить, что С++ — объектно-ориентированный язык программирования:

>class Shape { // ...

> virtual void Draw() const = 0; // Каждый производный

>                                // класс реализует свою функцию

>};

В качестве альтернативы (или в качестве дополнения) рассмотрим реализацию, которая следует совету по возможности принимать решения во время компиляции (см. рекомендацию 64):

>template

>void Draw(const S& shape) {

> shape.Draw(); // может быть виртуальной, а может и не быть

>};             // См. рекомендацию 64

Теперь ответственность за рисование каждой геометрической фигуры переходит к реализации самой фигуры, и синдром "не знаю, что делать с этим типом" просто невозможен.

Ссылки

[Dewhurst03] §69, §96 • [Martin96c] • [Meyer00] • [Stroustrup00] §12.2.5 • [Sutter04] §36

91. Работайте с типами, а не с представлениями

Резюме

Не пытайтесь делать какие-то предположения о том, как именно объекты представлены в памяти. Как именно следует записывать и считывать объекты из памяти — пусть решают типы объектов.

Обсуждение

Стандарт С++ дает очень мало гарантий по поводу представления типов в памяти.

• Целые числа используют двоичное представление.

• Для отрицательных чисел используется дополнительный код числа в двоичной системе.

• Обычные старые типы (Plain Old Data, POD[5]) имеют совместимое с С размещение в памяти: переменные-члены хранятся в порядке их объявления.

• Тип >int занимает как минимум 16 битов.

В частности, достаточно распространенные соглашения не гарантированы ни для всех имеющихся архитектур, ни тем более для архитектур, которые могут появиться в будущем. Так что не забывайте о следующем.

• Размер >int не равен ни 32 битам, ни какому-либо иному фиксированному размеру.

• Указатели и целые числа не всегда имеют один и тот же размер и не могут свободно преобразовываться друг в друга.

• Размещение класса в памяти не всегда приводит к размещению базового класса и членов в указанном порядке.


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


Геймдизайн. Рецепты успеха лучших компьютерных игр от Super Mario и Doom до Assassin’s Creed и дальше

Что такое ГЕЙМДИЗАЙН? Это не код, графика или звук. Это не создание персонажей или раскрашивание игрового поля. Геймдизайн – это симулятор мечты, набор правил, благодаря которым игра оживает. Как создать игру, которую полюбят, от которой не смогут оторваться? Знаменитый геймдизайнер Тайнан Сильвестр на примере кейсов из самых популярных игр рассказывает как объединить эмоции и впечатления, игровую механику и мотивацию игроков. Познакомитесь с принципами дизайна, которыми пользуются ведущие студии мира! Создайте игровую механику, вызывающую эмоции и обеспечивающую разнообразие.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.