Объектно-ориентированное проектирование с примерами

         

Состояние


Семантика. Рассмотрим торговый автомат, продающий напитки. Поведение такого объекта состоит в том, что после опускания в него монеты и нажатия кнопки автомат выдает выбранный напиток. Что произойдет, если сначала будет нажата кнопка выбора напитка, а потом уже опущена монета? Большинство автоматов при этом просто ничего не сделают, так как пользователь нарушил их основные правила.

Другими словами, автомат играл роль (ожидание монеты), которую пользователь игнорировал, нажав сначала кнопку. Или предположим, что пользователь автомата не обратил внимание на предупреждающий сигнал "Бросьте столько мелочи, сколько стоит напиток" и опустил в автомат лишнюю монету. В большинстве случаев автоматы не дружественны к пользователю и радостно заглатывают все деньги.

В каждой из таких ситуаций мы видим, что поведение объекта определяется его историей: важна последовательность совершаемых над объектом действий. Такая зависимость поведения от событий и от времени объясняется тем, что у объекта есть внутреннее состояние. Для торгового автомата, например, состояние определяется суммой денег, опущенных до нажатия кнопки выбора. Другая важная информация - это набор воспринимаемых монет и запас напитков.

На основе этого примера дадим следующее низкоуровневое определение:

Состояние объекта характеризуется перечнем (обычно статическим) всех свойств данного объекта и текущими (обычно динамическими) значениями каждого из этих свойств.

Одним из свойств торгового автомата является способность принимать монеты. Это статическое (фиксированное) свойство, в том смысле, что оно - существенная характеристика торгового автомата. С другой стороны, этому свойству соответствует динамическое значение, характеризующее количество принятых монет. Сумма увеличивается по мере опускания монет в автомат и уменьшается, когда продавец забирает деньги из автомата. В некоторых случаях значения свойств объекта могут быть статическими (например, заводской номер автомата), поэтому в данном определении использован термин "обычно динамическими".


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

Все свойства имеют некоторые значения. Эти значения могут быть простыми количественными характеристиками, а могут ссылаться на другой объект. Состояние лифта может описываться числом 3, означающим номер этажа, на котором лифт в данный момент находится. Состояние торгового автомата описывается в терминах других объектов, например, имеющихся в наличии напитков. Конкретные напитки - это самостоятельные объекты, отличные от торгового автомата (их можно пить, а автомат нет, и совершать с ними иные действия).

Таким образом, мы установили различие между объектами и простыми величинами: простые количественные характеристики (например, число 3) являются "постоянными, неизменными и непреходящими", тогда как объекты "существуют во времени, изменяются, имеют внутреннее состояние, преходящи и могут создаваться, уничтожаться и разделяться" [6].

Тот факт, что всякий объект имеет состояние, означает, что всякий объект занимает определенное пространство (физически или в памяти компьютера).

Примеры. Предположим, что на языке C++ нам нужно создать регистрационные записи о сотрудниках. Можно сделать это следующим образом:

struct PersonnelRecord { char name[100];
int socialSecurityNumber;


char department[10];
float salary;

};


Каждый компонент в приведенной структуре обозначает конкретное свойство нашей абстракции регистрационной записи. Описание определяет не объект, а класс, поскольку оно не вводит какой-либо конкретный экземпляр [Точнее, это описание определяет структуру в C++, семантика которой соответствует классу, у которого все поля открыты. Таким образом, структуры - это неинкапсулированные абстракции]. Для того чтобы создать объекты данного класса, необходимо написать следующее:

PersonnelRecord deb, dave, karen, jim, torn, denise, kaitlyn, krista, elyse;

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

class PersonnelRecord {
public: char* employeeName() const;
int employeeSocialSecurityNumber() const;
char* employeeDepartment() const;

protected: char name[100];
int socialSecurityNumber;
char department[10];
float salary;



};


Новое определение несколько сложнее предыдущего, но по ряду соображений предпочтительнее [К вопросу о стилях: по критериям, которые вводятся в этой главе далее, предложенное определение класса PersonnelRecord - это далеко не шедевр. Мы хотим здесь только показать семантику состояния класса. Иметь в классе функцию, которая возвращает значение char*, часто опасно, так как это нарушает парадигму защиты памяти: если метод отводит себе память, за которую получивший к ней доступ клиент не отвечает, результатом будет замусоривание памяти. В наших системах мы предпочитаем использовать параметризованный класс строк переменной длины, который можно найти в базовой библиотеке классов, вроде той, что описана в главе 9. И еще: классы - это больше чем структуры из С с синтаксисом классов C++; как объясняется в главе 4, классификация требует определенного согласования структуры и поведения].


В частности, в новом определении реализация класса скрыта от других объектов. Если реализация класса будет в дальнейшем изменена, код придется перекомпилировать, но семантически клиенты не будут зависеть от этих изменении (то есть их код сохранится). Кроме того, решается также проблема занимаемой объектом памяти за счет явного определения операций, которые разрешены клиентам над объектами данного класса. В частности, мы даем всем клиентам право узнать имя, код социальной защиты и место работы сотрудника, но только особым клиентам (а именно, подклассам данного класса) разрешено устанавливать значения указанных параметров. Только этим специальным клиентам разрешен доступ к сведениям о заработной плате. Другое достоинство последнего определения связано с возможностью его повторного использования. В следующем разделе мы увидим, что механизм наследования позволяет повторно использовать абстракцию, а затем уточнить и многими способами специализировать ее.

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


Содержание раздела