- •Глава 6 посвящена понятию производных классов, которое позволяет строить
- •Раздел 3.4 главы 2. Для обозначения справочного руководства применяется
- •1991 Г.Г. (такие как множественное наследование, статические функции-члены
- •1.1 Введение
- •1.2 Парадигмы программирования
- •1.2.1 Процедурное программирование
- •1.2.5 Объектно-ориентированное программирование
- •1.5 Поддержка объектно-ориентированного программирования
- •1.5.1 Механизм вызова
- •1.5.2 Проверка типа
- •1.5.3 Множественное наследование
- •1.6 Пределы совершенства
- •2.2 Имена
- •2.3.2 Неявное преобразование типа
- •2.4 Литералы
- •2.4.4 Строки
- •2.6. Экономия памяти
- •2.6.1 Поля
- •3.1.1 Анализатор
- •3.1.2 Функция ввода
- •3.2 Сводка операций
- •3.2.3 Инкремент и декремент
- •3.2.5 Преобразование типа
- •3.2.6 Свободная память
- •3.3.2 Оператор goto
- •4.1 Введение
- •4.3.1 Единственный заголовочный файл
- •4.3.2 Множественные заголовочные файлы
- •4.4 Связывание с программами на других языках
- •4.6.3 Передача параметров
- •5.1 Введение и краткий обзор
- •5.3.1 Альтернативные реализации
- •5.3.2 Законченный пример класса
- •Vector и matrix, мы могли бы обойтись без контроля индекса при
- •5.4.5 Указатели на члены
- •5.4.6 Структуры и объединения
- •5.5.3 Свободная память
- •5.5.5 Массивы объектов класса
- •6.1 Введение и краткий обзор
- •6.2.3 Иерархия классов
- •6.2.4 Поля типа
- •6.4.1 Монитор экрана
- •6.5 Множественное наследование
- •7.1 Введение
- •7.3 Пользовательские операции преобразования типа
- •7.3.2 Операции преобразования
- •7.3.3 Неоднозначности
- •7.5 Большие объекты
- •Void f2(t a) // вариант с контролем
- •Void f3(t a) // вариант с контролем
- •Inv() обращает саму матрицу m, а не возвращает новую, обратную m,
- •7.13 Предостережения
- •8.1 Введение
- •8.4.4 Неявная передача операций
- •8.4.5 Введение операций с помощью параметров шаблонного класса
- •8.7.1 Задание реализации с помощью параметров шаблона
- •9.1 Обработка ошибок
- •9.1.2 Другие точки зрения на особые ситуации
- •9.3.2 Производные особые ситуации
- •9.4.2 Предостережения
- •9.4.3 Исчерпание ресурса
- •9.4.4 Особые ситуации и конструкторы
- •9.5 Особые ситуации могут не быть ошибками
- •10.1 Введение
- •10.2 Вывод
- •10.2.1 Вывод встроенных типов
- •10.4.1.2 Поля вывода
- •10.4.1.4 Вывод целых
- •Istream - шаблон типа smanip, а smanip - двойник для ioss.
- •10.5.1 Закрытие потоков
- •10.5.2 Строковые потоки
- •X Целый параметр выдается в шестнадцатеричной записи;
- •11.1 Введение
- •11.2 Цели и средства
- •11.3 Процесс развития
- •11.3.1 Цикл развития
- •11.3.2 Цели проектирования
- •11.3.3 Шаги проектирования
- •11.3.3.1 Шаг 1: определение классов
- •11.3.3.2 Шаг 2: определение набора операций
- •11.3.3.3 Шаг 3: указание зависимостей
- •11.3.3.4 Шаг 4: определение интерфейсов
- •11.3.3.5 Перестройка иерархии классов
- •11.3.3.6 Использование моделей
- •11.3.4 Эксперимент и анализ
- •11.3.5 Тестирование
- •11.3.6 Сопровождение
- •11.3.7 Эффективность
- •11.4 Управление проектом
- •11.4.1 Повторное использование
- •11.4.2 Размер
- •11.4.3 Человеческий фактор
- •11.5 Свод правил
- •11.6 Список литературы с комментариями
- •12.1 Проектирование и язык программирования.
- •12.1.1 Игнорирование классов
- •12.1.2 Игнорирование наследования
- •12.1.3 Игнорирование статического контроля типов
- •12.1.4 Гибридный проект
- •12.2 Классы
- •12.2.1 Что представляют классы?
- •12.2.2 Иерархии классов
- •12.2.3 Зависимости в рамках иерархии классов.
- •Vertical_scrollbar или с помощью одного типа scrollbar, который
- •12.2.6 Отношения использования
- •12.2.7 Отношения внутри класса
- •12.3 Компоненты
- •12.4 Интерфейсы и реализации
- •12.5 Свод правил
- •13.1 Введение
- •13.2 Конкретные типы
- •13.4 Узловые классы
- •1, 2, 6 И 7. Класс, который не удовлетворяет условию 6, походит
- •13.5.1 Информация о типе
- •13.6 Обширный интерфейс
- •13.7 Каркас области приложения
- •13.8 Интерфейсные классы
- •13.10 Управление памятью
12.4 Интерфейсы и реализации
Идеальный интерфейс должен
- представлять полное и согласованное множество понятий для
пользователя,
- быть согласованным для всех частей компонента,
- скрывать специфику реализации от пользователя,
- допускать несколько реализаций,
- иметь статическую систему типов,
- определяться с помощью типов из области приложения,
- зависеть от других интерфейсов лишь частично и вполне определенным
образом.
Отметив необходимость согласованности для всех классов, которые
образуют интерфейс компонента с остальным миром, мы можем упростить
вопрос интерфейса, рассмотрев только один класс, например:
class X { // пример плохого определения интерфейса
Y a;
Z b;
public:
void f(const char* ...);
void g(int[],int);
void set_a(Y&);
Y& get_a();
};
В этом интерфейсе содержится ряд потенциальных проблем:
-Типы Y и Z используются так, что определения Y и Z должны быть
известны во время трансляции.
- У функции X::f может быть произвольное число параметров
неизвестного типа (возможно, они каким-то образом контролируются
"строкой формата", которая передается в качестве первого
параметра).
- Функция X::g имеет параметр типа int[]. Возможно это нормально,
но обычно это свидетельствует о том, что определение слишком
низкого уровня абстракции. Массив целых не является достаточным
определением, так как неизвестно из скольких он может
состоять элементов.
- Функции set_a() и get_a(), по всей видимости, раскрывают
представление объектов класса X, разрешая прямой доступ
к X::a.
Здесь функции-члены образуют интерфейс на слишком низком уровне
абстракции. Как правило классы с интерфейсом такого уровня относятся
к специфике реализации большого компонента, если они вообще могут
к чему-нибудь относиться. В идеале параметр функции из интерфейса
должен сопровождаться такой информацией, которой достаточно
для его понимания. Можно сформулировать такое правило: надо уметь
передавать запросы на обслуживание удаленному серверу по узкому
каналу.
Язык С++ раскрывает представление класса как часть интерфейса.
Это представление может быть скрытым (с помощью private или
protected), но обязательно доступным транслятору, чтобы он мог разместить
автоматические (локальные) переменные, сделать подстановку тела
функции и т.д. Отрицательным следствием этого является то, что
использование типов классов в представлении класса может привести к
возникновению нежелательных зависимостей. Приведет ли использование
членов типа Y и Z к проблемам, зависит от того, каковы в действительности
типы Y и Z. Если это достаточно простые типы, наподобие complex или
String, то их использование будет вполне допустимым в большинстве случаев.
Такие типы можно считать устойчивыми, и необходимость включать
определения их классов будет вполне допустимой нагрузкой для транслятора.
Если же Y и Z сами являются классами интерфейса большого
компонента (например, типа графической системы или системы обеспечения
банковских счетов), то прямую зависимость от них можно считать
неразумной. В таких случаях предпочтительнее использовать член,
являющийся указателем или ссылкой:
class X {
Y* a;
Z& b;
// ...
};
При этом способе определение X отделяется от определений Y и Z, т.е.
теперь определение X зависит только от имен Y и Z. Реализация X,
конечно, будет по-прежнему зависеть от определений Y и Z, но это
уже не будет оказывать неблагоприятного влияния на пользователей X.
Вышесказанное иллюстрирует важное утверждение: У интерфейса,
скрывающего значительный объем информации (что и должен делать полезный
интерфейс), должно быть существенно меньше зависимостей, чем
у реализации, которая их скрывает. Например, определение класса X
можно транслировать без доступа к определениям Y и Z. Однако,
в определениях функций-членов класса X, которые работают со
ссылками на объекты Y и Z, доступ к определениям Y и Z необходим.
При анализе зависимостей следует рассматривать раздельно
зависимости в интерфейсе и в реализации. В идеале для обоих видов
зависимостей граф зависимостей системы должен быть направленным
нецикличным графом, что облегчает понимание и тестирование
системы. Однако, эта цель более важна и чаще достижима для
реализаций, чем для интерфейсов.
Отметим, что класс определяет три интерфейса:
class X {
private:
// доступно только для членов и друзей
protected:
// доступно только для членов и друзей, а также
// для членов и друзей производных классов
public:
// общедоступно
};
Члены должны образовывать самый ограниченный из возможных интерфейсов.
Иными словами, член должен быть описан как private, если нет
причин для более широкого доступа к нему; если же таковые есть, то
член должен быть описан как protected, если нет дополнительных причин
задать его как public. В большинстве случаев плохо задавать все данные,
представляемые членами, как public. Функции и классы, образующие общий
интерфейс, должны быть спроектированы таким образом, чтобы представление
класса совпадало с его ролью в проекте как средства представления
понятий. Напомним, что друзья являются частью общего интерфейса.
Отметим, что абстрактные классы можно использовать для
представления понятия упрятывания более высокого уровня ($$1.4.6,
$$6.3, $$13.3).