Интерфейсы
Статические элементы класса
Обычно для каждого объекта необходима своя копия переменных, описанных в классе. Однако в некоторых ситуациях требуется, чтобы в классе были данные, общие для всех его экземпляров – переменные класса.
Переменная класса (статическая переменная) в С++ описывается с ключевым словом static, она создается один раз как часть класса, а не для каждого конкретного экземпляра данного класса. Функция, которой требуется доступ к статическим переменным, но не требуется, чтобы она вызывалась для конкретного экземпляра класса, также описывается как статическая (static).
Обращаться к статическим элементам можно, как к обычным элементам, через любой объект, а также без указания объекта с использованием квалифицированного имени.
Статические переменные должны быть дополнительно описаны и инициализированы вне определения класса как глобальные переменные (даже если первоначально они описаны в закрытой или защищенной части класса), после инициализации к ним можно обращаться даже до определения объектов данного класса.
При наследовании статические атрибуты наследуются, но это атрибуты суперкласса, единые для всех подклассов.
Пример. Используем статическую переменную для подсчета объектов-фигур, потомков класса Shape.
class Shape {
static int count; // счетчик фигур – статическая переменная
. . .
public:
Shape( ) {count++;}
~ Shape( ) {count--;}
static int get_count( ) {return count;} // статическая get-функция
. . .
};
int Shape :: count = 0; // описание и инициализация вне класса
. . .
int c; Circle C; Triangle T; SolidCircle SC;
c = C.get_count( ); // три эквивалентных
c = Shape :: get_count( ); // обращения к
c = Circle :: get_count( ); // статической функции
Хотя класс Shape и является абстрактным, мы описали в нем конструктор и деструктор, цель которых состоит лишь в изменении статической переменной – счетчика. При объявлении объекта подкласса будет вызван конструктор класса Shape, а при уничтожении – деструктор. По этой причине изменять счетчик в конструкторе и деструкторе производных классов не нужно.
Статические члены используются также для реализации на языке С++ утилит. Утилитами называют совокупность глобальных переменных и свободных подпрограмм, сгруппированных в форме объявления класса. В этом случае глобальные переменные и свободные подпрограммы рассматриваются как члены класса, причем именно как статические. Введение утилит позволяет приблизить реализацию системы на языке С++ к набору классов и взаимодействующих объектов, как в чисто объектно-ориентированных языках.
Задача.Что напечатает программа?
class Point {public: static int count;. . .};
int Point :: count=0;
void main( ) {
Point P1, P2;
P1.count =1; P2.count =2; printf("%d\n", P2.count);
P1.count++; printf("%d\n", P1.count);
}
Когда с помощью объектно-ориентированного подхода начали разрабатывать крупные программные системы, выяснилось, что кроме классов нужны дополнительные уровни абстракции. В частности, если сложный объект имеет разное поведение (играет разные роли), в зависимости от того, с кем он взаимодействует, то бывает удобно скрыть все функции, не нужные в данный момент. А точнее: на время данного взаимодействия сделать доступными все необходимые функции и только их. Для описания таких групп функций удобно использовать понятие интерфейса. В данном контексте интерфейс удобно рассматривать как абстрактный класс, содержащий только чисто виртуальные функции и не имеющий собственных данных.
Пример. Все элементы управления телевизором можно разделить на несколько групп: пользовательские (громкость, номер канала), специальные (частота канала) и аппаратные (параметры электрических цепей). При этом пользователь работает с пользовательскими органами управления, настройщик – со специальными, а телемастер – с аппаратными. При этом, если телевизор исправен и настроен, пользователю нет необходимости видеть и менять состояние специальных и аппаратных органов управления. Поэтому пользовательские элементы управления обычно выносятся на переднюю панель телевизора, специальные закрыты небольшой дверцей, а аппаратные вообще погружены внутрь корпуса. Если бы все было на поверхности, пользователь мог бы сделать все то же, что и раньше, но для него оказались бы доступными специальные и аппаратные органы управления, и он мог бы случайно испортить настройки. Кроме того, передняя панель была бы загромождена настолько, что мало кто смог бы ориентироваться в обилии кнопок, ручек и т.п.
Между интерфейсами могут существовать отношения наследования, ассоциации и зависимости, аналогичные одноименным отношениям между классами. Между классом и интерфейсом могут существовать отношения реализации и зависимости.Будем говорить, что класс реализует(или поддерживает)интерфейс, если он содержит методы, реализующие все операции интерфейса. Интерфейс может реализовываться несколькими классами (например, пользовательские элементы радиоприемника и телевизора совпадают), а класс может реализовывать несколько интерфейсов (например, телевизор реализует три интерфейса). С другой стороны, класс может зависеть от нескольких интерфейсов, при этом предполагается, что какие-то классы эти интерфейсы реализуют.
# define interface struct // функции – элементы интерфейса – открыты
interface IUser{ // пользовательский интерфейс
virtual void change_channel(int)=0;
virtual void change_sound(int)=0;
};
class Televisor: public IUser, public ISpecial, public IApparatus {
// класс Televisor реализует пользовательский, специальный и
// аппаратный интерфейсы
int channel, sound_level;
. . .
public:
virtual void change_channel(int number) {channel = number;}
virtual void change_sound(int rel) {sound += rel;}
. . .
};
class User { // класс User зависит от интерфейса IUser
public: User(IUser *IU);
. . .
};
. . .
Televisor* My_TV; . . .
User My(My_TV);
На рис. 4.3 показано, что пользователь взаимодействует с телевизором посредством интерфейса IUser, а телемастер – посредством интерфейса IApparatus.
Рис. 4.3. Интерфейсы