Переменные и операции класса
Обычно для каждого объекта необходима своя копия переменных, описанных в классе. Однако в некоторых ситуациях требуется, чтобы в классе были данные, общие для всех его экземпляров – переменные класса.
Переменная класса в С++ описывается с ключевым словом static, она создается один раз как часть класса, а не для каждого конкретного экземпляра данного класса. Функция, которой требуется доступ к переменным класса, но не требуется, чтобы она вызывалась для конкретного экземпляра класса, также описывается как статическая (static).
Пример. Опишем класс для работы с датами.
class Date{
int day;// день
int month;// месяц
int year;// год
public:
Date (int, int, int); // день, месяц, год
Date (int, int); // день, месяц, текущий год – по умолчанию
Date (int); // день, текущие месяц и год – по умолчанию
Date ( ); // дата по умолчанию – сегодня
. . .
};
В данном примере мы объявили четыре конструктора для того, чтобы иметь возможность задавать дату в сокращенном виде. В крупных проектах количество конструкторов, позволяющее учесть различные варианты использования, может быть намного больше.
Рассмотрим альтернативное решение. Опишем конструктор с аргументами по умолчанию.
class Date{
int day, month, year;
static Date default_date;
public:
Date (int d=0, int m=0, int y=0);
. . .
static void set_default(int, int, int);
};
Если значение аргумента является нулевым, следует воспользоваться соответствующим элементом даты, задаваемой по умолчанию – default_date. Поскольку значение default_date должно быть одинаковым для всех объектов класса, данная переменная является статическим членом. Для ее инициализации описана также статическая функция-член set_default. Статические члены – и функции, и данные – должны быть где-то определены. Например,
Date Date :: default_date (1, 1, 2000);
void set_default (int d, int m, int y)
{
Date :: default_date= Date (d, m, y);
}
Статические члены используются также для реализации на языке С++ утилит. Утилитами называют совокупность глобальных переменных и свободных подпрограмм, сгруппированных в форме объявления класса. В этом случае глобальные переменные и свободные подпрограммы рассматриваются как члены класса, причем именно как статические. Введение утилит позволяет приблизить реализацию системы на языке С++ к набору классов и взаимодействующих объектов, как в чисто объектно-ориентированных языках.
Интерфейсы
Когда с помощью объектно-ориентированного подхода начали разрабатывать крупные программные системы, выяснилось, что кроме классов нужны дополнительные уровни абстракции. В частности, если сложный объект имеет разное поведение, в зависимости от того, с кем он взаимодействует, то бывает удобно скрыть все функции, не нужные в данный момент. А точнее: на время данного взаимодействия сделать доступными все необходимые функции и только их. Для описания таких групп функций удобно использовать понятие интерфейса. В данном контексте интерфейс удобно рассматривать как абстрактный класс, не имеющий собственных данных.
Пример. Все элементы управления телевизора можно разделить на несколько групп: пользовательские (громкость, номер канала), специальные (частота канала) и аппаратные (параметры электрических цепей). При этом пользователь работает с пользовательскими органами управления, настройщик – со специальными, а телемастер – с аппаратными. При этом, если телевизор исправен и настроен, пользователю нет необходимости видеть и менять состояние специальных и аппаратных органов управления. Поэтому пользовательские элементы управления обычно выносятся на переднюю панель телевизора, специальные закрыты небольшой дверцей, а аппаратные вообще погружены внутрь корпуса. Если бы все было на поверхности, пользователь мог бы сделать все то же, что и раньше, но для него оказались бы доступными специальные и аппаратные органы управления, и он мог бы случайно испортить настройки. Кроме того, передняя панель была бы загромождена настолько, что мало кто смог бы ориентироваться в обилии кнопок, ручек и т.п.
Между интерфейсами могут существовать отношения обобщения, ассоциации и зависимости, аналогичные одноименным отношениям между классами. Между классом и интерфейсом могут существовать отношения реализации и зависимости.Будем говорить, что класс реализует(или поддерживает)интерфейс, если он содержит методы, реализующие все операции интерфейса. Интерфейс может реализовываться несколькими классами, а класс может реализовывать несколько интерфейсов. С другой стороны, класс может зависеть от нескольких интерфейсов, при этом предполагается, что какие-то классы эти интерфейсы реализуют.
На рис. 4.3–4.4 показано, что пользователь взаимодействует с телевизором посредством интерфейса IUser, а телемастер – посредством интерфейса IApparatus.
Рис. 4.3. Интерфейс пользователя Рис. 4.4. Интерфейс телемастера
Группирование классов
Когда система разрастается до десятка классов, можно заметить группы классов, связанные внутри и слабо зацепляющиеся с другими. Такие группы классов образуют пакет. Пакетом в области объектно-ориентированных технологий называют общий механизм организации элементов в группы. В данном контексте мы будем говорить только о группировании классов и называть пакетом группы, содержащие классы и другие пакеты.
Пакет не имеет операций или состояний в явном виде, они содержатся в нем неявно в описаниях агрегированных классов.
Некоторые классы в пакете могут быть открытыми, то есть экспортироваться для использования за пределы пакета. Остальные классы могут быть частью реализации, то есть не использоваться никакими классами, внешними к этому пакету.
В C++ пакеты классов реализуются с помощью введения пространств имен – namespace. Однако пространство имен позволяет реализовать более широкое понятие. Оно может включать в себя классы, другие пространства имен, свободные подпрограммы и глобальные (внутри пространства имен) данные.
Пример. Объединим все классы, разработанные для использования в графической системе в одну компоненту. Предоставим пользователю описания в файле GraphSys.h:
namespace GraphSys{
class Point{ ... };
class Color{ ... };
class Shape {... };
class Circle: public Shape{... };
class Triangle: public Shape {... };
class Square: public Shape{... };
class SolidCircle: public Circle {... };
}
Реализация указанных классов находится в файле GraphSys.срр:
namespace GraphSys{
Circle :: draw ( ){...}
Triangle :: draw ( ){...}
Square :: draw ( ){...}
SolidCircle :: draw ( ){...}
. . .
}
Обращение к членам пространства имен осуществляется с использованием явной квалификации:
GraphSys :: Circle C;
GraphSys :: SolidCircle SC;
С другой стороны, описание using в пользовательском коде позволяет не использовать все время явную квалификацию:
#include GraphSys.h
using namespace GraphSys;
void user_func ( ){
Circle C;
. . .
C -> draw ( );
}