Один из наиболее фундаментальных принципов разработки хорошего программного обеспечения состоит в отделении интерфейса от реализации. Это облегчает модификацию программ. Что касается клиентов класса, то изменения в реализации класса не влияют на клиента до тех пор, пока интерфейс класса, изначально предназначенный для клиента, остается неизменным (функции класса можно расширять за пределы исходного интерфейса).
Помещайте объявление класса в заголовочный файл, чтобы оно было доступно любому клиенту, который захочет использовать класс. Это формирует открытый интерфейс класса. Помещайте определения функций-элементов класса в исходный файл. Это формирует реализацию класса.
Клиенты класса не нуждаются в доступе к исходному коду класса для того, чтобы использовать класс. Однако, клиенты должны иметь возможность связаться с объектным кодом класса.
Это стимулирует независимых продавцов программного обеспечения поставлять библиотеки классов для продажи или лицензирования. Продавцы поставляют в своих продуктах только заголовочные файлы и объектные модули. Не выдается никакой оригинальной, патентоспособной информации, как это было бы в случае поставки исходных кодов. Сообщество пользователей С++ извлекает выгоду, имея в своем распоряжении большинство библиотек классов, поставляемых независимыми продавцами.
На самом деле все выглядит не в таком розовом свете. Заголовочные файлы содержат некоторую часть реализации и краткие сведения о других частях реализации. Встраиваемые функции-элементы, например, должны находиться в заголовочном файле, так что когда компилятор компилирует клиента, клиент может включить определение встраиваемой функции inline. Закрытые элементы перечисляются в определении класса в заголовочном файле, так что эти элементы видимы для клиентов, несмотря на то, что клиенты не могут иметь к ним доступа.
Информация, важная для интерфейса класса, должна включаться в заголовочный файл. Информация, которая будет использоваться только внутри класса и которая не является необходимой для клиентов класса, должна включаться в неоглашаемый исходный файл. Это еще один пример принципа наименьших привилегий.
Рисунок 6.5 разбивает программу на рис. 6.3 на ряд файлов. При построении программы на С++ каждое определение класса обычно помещается в заголовочный файл, а определения функций-элементов этого класса помещаются в файлы исходных кодов с теми же базовыми именами. Заголовочные файлы включаются (посредством #include) в каждый файл, в котором используется класс, а файлы с исходными кодами компилируются и компонуются с файлом, содержащим главную программу. Посмотрите документацию на ваш компилятор, чтобы определить, как компилировать и компоновать программы, содержащие множество исходных файлов.
Программа на рис. 6.5 состоит из заголовочного файла timel.h, в котором объявляется класс Time, файла timel.cpp, в котором описываются функции- элементы класса Time, и файла fig6_5.cpp, в котором описывается функция main. Выходные данные этой программы идентичны выходным данным программы на рис. 6.3.
// TIME1.H
// Объявление класса Time.
// Функции-элементы определены в TIME.CPP
// Предотвращение многократного включения заголовочного файла #ifndef Т1МЕ1_Н #define Т1МЕ1_Н
// Определение абстрактного типа данных Time
class Time { public:
Time(); // конструктор
void setTime(int, int, int); // установка часов, минут,
// секунд
void printMilitary(); //печать времени в военном формате void printStandard(); //печать времени в стандартном формате
// | 0 - | - 23 |
// | 0 - | - 59 |
// | 0 - | - 59 |
private:
int hour; int minute; int second;
} ;
#endif
Рис. 6.5. Заголовочный файл класса Time (часть 1 из 3)
// TIME1.CPP
// Определения функций-элементов для класса Time.
iinclude iinclude "timel.h"
// Конструктор Time присваивает нулевые начальные значения каждому //элементу данных. Обеспечивает согласованное начальное состояние //всех объектов Time
Time::Time() { hour = minute = second =0; }
// Задание нового значения Time в виде военного времени. // Проверка правильности значений данных. Обнуление неверных //значений.
void Time::setTime(int h, int m, int s) {
hour = (h >= 0 && h < 24) ? h : 0; minute = (m >= 0 && m < 60) ?m: 0; second = (s >= 0 && s < 60) ?s : 0;
// Печать времени в военном формате void Time::printMilitary()
{
"0" "0" |
cout << « << |
(hour < 10 ? (minute < 10 (second < 10 ') « hour « ":" "") « minute « "") « second;
// Печать времени в стандартном формате
void Time::printStandard() {
cout « ((hour == 0 || hour == 12 ) ? 12 : hour % 12) « ":" « (minute < 10 ? "0" : "") « minute « ":" « (second < 10 ? "0" : "") « second « (hour < 12 ? " AM" : " PM");
}
Рис. 6.5. Исходный файл определений функций-элементов класса Time (часть 2 из 3)
// FIG6_5.СРР // Драйвер класса Timel
// ЗАМЕЧАНИЕ: Компилируется вместе с TIME1.CPP iinclude #include "timel.h"
// Драйвер для проверки простого класса Time
main() {
Time t; // определение экземпляра объекта t класса Time
cout << "Начальное значение военного времени равно "; t.printMilitary(); cout « endl
« "Начальное значение стандартного времени равно "; t.printStandard();
t.setTime (13, 27, 6);
cout « endl << endl « "Военное время после setTime равно "; t.printMilitary();
cout « endl << "Стандартное время после setTime равно "; t.printStandard();
t.setTime(99, 99, 99); // попытка установить
// неправильные значения
cout << endl « endl
« "После попытки неправильной установки: " « endl << "Военное время: "; t.printMilitary();
cout << endl << "Стандартное время: "; t.printStandard(); cout « endl; return 0;
}
Начальное значение военного времени равно 00:00:00 Начальное значение стандартного времени равно 12:00:00 AM
Военное время после setTime равно 13:27:06 Стандартное время после setTime равно 1:27:06 РМ
После попытки неправильной установки: Военное время: 00:00:00 Стандартное время: 12:00:00 AM
Заметим, что объявление класса заключено в следующие директивы препроцессора (смотри главу 17):
// Предотвращение многократного включения заголовочного файла fifndef Т1МЕ1_Н fdefine Т1МЕ1_Н
#endif
При построении больших программ в заголовочные файлы будут помещаться также и другие определения и объявления. Приведенные выше директивы препроцессора предотвращают включение кода между #ifndef и #endif, если определено имя Т1МЕ1_Н. Если заголовок еще не включался в файл, то имя Т1МЕ1_Н определяется директивой #define и операторы заголовочного файла включаются в результирующий файл. Если же заголовок уже был включен ранее, Т1МЕ1_Н уже определен и операторы заголовочного файла повторно не включается. Попытки многократного включения заголовочного файла обычно случаются в больших программах с множеством заголовочных файлов, которые могут сами включать другие заголовочные файлы. Замечание: по негласному соглашению в приведенных выше директивах используется имя символической константы, представляющее собой просто имя заголовочного файла с символом подчеркивания вместо точки.
Хороший стиль программирования 6.2
Используйте директивы препроцессора #ifndef, #define и #endif для того, чтобы избежать включения в программу заголовочных файлов более одного раза.
Хороший стиль программирования 6.3
Используйте имя заголовочного файла с символом подчеркивания вместо точки в директивах препроцессора #ifndef и #define заголовочного файла.
Комментариев нет:
Отправить комментарий