Выбрать главу

В разделе 2.4 мы рассматривали реализацию иерархии классов IntArray. Синтаксическая структура определения иерархии, изображенной на рис. 17.1, аналогична:

class Query { ... };

class AndQuery : public Query { ... };

class OrQuery : public Query { ... };

class NotQuery : public Query { ... };

class NameQuery : public Query { ... };

Наследование задается с помощью списка базовых классов. В случае одиночного наследования этот список имеет вид:

: уровень-доступа базовый-класс

где уровень-доступа - это одно из ключевых слов public, protected, private (смысл защищенного и закрытого наследования мы обсудим в разделе 18.3), а базовый-класс - имя ранее определенного класса. Например, Query является открытым базовым классом для любого из четырех классов запросов.

Класс, встречающийся в списке базовых, должен быть предварительно определен. Следующего опережающего объявления Query недостаточно для того, чтобы он мог выступать в роли базового:

// ошибка: Query должен быть определен

class Query;

class NameQuery : piblic Query { ... };

Опережающее объявление производного класса должно включать только его имя, но не список базовых классов. Поэтому следующее опережающее объявление класса NameQuery приводит к ошибке компиляции:

// ошибка: опережающее объявление не должно

// включать списка базовых классов

class NameQuery : public Query;

Правильный вариант в данном случае выглядит так:

// опережающее объявление как производного,

// так и обычного класса содержит только имя класса

class Query;

class NameQuery;

Главное различие между базовыми классами Query и IntArray (см. раздел 2.4) состоит в том, что Query не представляет никакого реального объекта в нашем приложении. Пользователи класса IntArray вполне могут определять и использовать объекты этого типа непосредственно. Что же касается Query, то разрешается определять лишь указатели и ссылки на него, используя их для косвенного манипулирования объектами производных классов. О Query говорят, что это абстрактный базовый класс. В противоположность этому IntArray является конкретным базовым классом. Преобладающей формой в объектно-ориентированном проектировании является определение абстрактного базового класса типа Query и одиночное открытое наследование ему.

Упражнение 17.1

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

книга аудио-книга

аудиокассета детская кукла

видеокассета видеоигра для приставки SEGA

книга с подневной оплатой видеоигра для приставки SONY

книга на компакт-диске видеоигра для приставки Nintendo

Упражнение 17.2

Выберите или придумайте собственную абстракцию, содержащую семейство типов. Организуйте типы в иерархию наследования:

(a) Форматы графических файлов (gif, tiff, jpeg, bmp и т.д.)

(b) Геометрические примитивы (прямоугольник, круг, сфера, конус и т.д.)

(c) Типы языка C++ (класс, функция, функция-член и т.д.)

17.2. Идентификация членов иерархии

В разделе 2.4 мы уже упоминали о том, что в объектном проектировании обычно есть один разработчик, который конструирует и реализует класс, и много пользователей, применяющих предоставленный открытый интерфейс. Это разделение ответственности отразилось в концепции открытого и закрытого доступа к членам класса.

Когда используется наследование, у класса оказывается множество разработчиков. Во-первых, тот, кто предоставил реализацию базового класса (и, возможно, некоторых производных от него), а во-вторых, те, кто разрабатывал производные классы на различных уровнях иерархии. Этот род деятельности тоже относится к проектированию. Разработчик подтипа часто (хотя и не всегда) должен иметь доступ к реализации базового класса. Чтобы разрешить такой вид доступа, но все же предотвратить неограниченный доступ к деталям реализации класса, вводится дополнительный уровень доступа - protected (защищенный). Данные и функции-члены, помещенные в секцию protected некоторого класса, остаются недоступными вызывающей программе, но обращение к ним из производных классов разрешено. (Все находящееся в секции private базового класса доступно только ему, но не производным.)

Критерии помещения того или иного члена в секцию public одинаковы как для объектного, так и для объектно-ориентированного проектирования. Меняется только точка зрения на то, следует ли объявлять член закрытым или защищенным. Член базового класса объявляется закрытым, если мы не хотим, чтобы производные классы имели к нему прямой доступ; и защищенным, если его семантика такова, что для эффективной реализации производного класса может потребоваться прямой доступ к нему. При проектировании класса, который предполагается использовать в качестве базового, надо также принимать во внимание особенности функций, зависящих от типа, - виртуальных функций в иерархии классов.