В предыдущеи главе были наложены основы программирования на управляемом C++. Учитывая ваш богатый опыт работы с C++ бьпо охвачено много материала В этой паве мы изменим темп вместо того, чтобы осваивать новый мат ерши потратим ботьше времени на углубление понимания объектно ориентированной стороны управляемого С++ в частности, абстракции и наследования Сначала мы повторим основы объектно-ориентированного программирования, потом рассмотрим пример Бюро путешествии Acme. Этот пример будет разрабатываться в последующих павах по мере изучения татформы NET Мы рассмотрим, какие абстракции подходят для того чтобы реализовать систему резервирования ресурсов, и реализуем систему резервирования (бронирования) гостиничных номеров В абстрактные базовые классы мы поместим повторно иелользхемыи код с помощью которого можно легко реализовать и другие системы резервирования Вы увидите что правильное использование абстракции — ключ к такой реализации. Вы также знаете какие особенности мтравллемого C++ особенно полезны для объектно ориентированного программирования Эти особенно сти а именно, контроль за доступом (модификаторы достлпа public (общелрстлпный) private (частный) и protected (защищенный)) и использование свойств дают возможность созывать надежные и удобные в употреблении абстракции
Эта часть главы— встлпление в ней мы рассмотрим основы объектно ориентированного программирования Если у вас есть опыт работы с объектно ориентированным С++, можете пропустить вступление и перейти к рассмотрению примера. С другои стороны если вы раньше использовали C++ просто как улучшенныи вариант С++ , вам будет полезно прочитать встуаление, для того чтобы научиться использовать С++ в качестве объектно-ориентированного языка и научиться строить иерархии наследования которые являются моделями реальных систем
Объекты имеют значение не только в мире программ, но и в реальном мире. Объектная модель описывает взаимосвязь между объектами.
Объекты в реальном мире
Значение термина объект в реальном мире интуитивно понятно. Существуют конкретные, материальные осязаемые объекты (мяч, автомобиль, самолет), и более абстрактные объекты, которые представляют собой реализацию некоторых понятий (комитет, патент, страховой контракт).
У объектов есть атрибуты (характеристики), причем к объектам можно применять некоторые операции. У мяча есть размер, вес, цвет, и т.д. С мячом можно проделать некоторые действия, например, бросить, поймать и уронить.
Между классами объектов могут существовать разные отношения. Одно из таких отношений— это отношение специализации (конкретизации), например автомобиль — это один конкретный вид из различных средств передвижения. Существуют также отношения "часть—целое"; например, автомобиль состоит из двигателя, шасси, колес и т.д.
Объектные модели
Объекты могут также использоваться в программах. Они служат для реализации программной модели системы из реального мира. Программные объекты являются абстракциями объектов реального мира, они описывают те свойства реальных объектов, которые необходимы для решения данной задачи. Потом с помощью языка программирования можно реализовать программную модель системы из реального мира. Программная модель реализуется таким образом, чтобы наиболее точно моделировать реальную систему и чтобы ее можно было без труда изменить, если изменится реальная система.
Существуют формальные языки для описания объектных моделей. Самый популярный из них — это универсальный язык моделирования UML (Unified Modeling Language), который получился в результате синтеза нескольких более ранних языков моделирования. Описание формальных языков моделирования не является предметом этой книги, и потому мы будем использовать неформальные модели.
Многократно используемые компоненты
Еще одним преимуществом объектов в программировании является то, что с их помощью можно создавать многократно используемые компоненты. При проектировании аппаратных средств ЭВМ уже длительное время извлекается существенная выгода от применения компонентов аппаратных средств ЭВМ многократного использования. Например, компьютер можно собрать из блока питания, печатных плат и других комплектующих. Печатные платы можно собрать из отдельных чипов. Такие же чипы можно использовать и в других компьютерах, и потому новое оборудование не приходится проектировать с самого начала. Подходящая технология позволяет реализовать возможность такого повторного использования и в программном обеспечении. Благодаря объектам можно повторно использовать программы.
Объекты в программах
Объект — это элемент программы, состоящий из членов-данных (состояние) и функций для работы с ними (поведение), который рассматривается как независимый модуль. Например, объект HotelBroker может хранить в себе список гостиниц (состояние) и функции, с помощью которых можно добавить гостиницу в список и зарезервировать гостиничный номер (поведение).
Абстракция
Абстракция передает основные свойства объекта реального мира, опуская ненужные детали. Все экземпляры абстрактного объекта имеют эти общие свойства. Абстракция позволяет уменьшить сложность задачи. Рассмотрим, например, проблему резервирования: можно забронировать гостиничный номер, билет на авиарейс, или зарезервировать зал для совещаний. Резервирование всех этих услуг проводится по-разному, но есть общие свойства, например, объем резервирования (количество мест в гостинице, количество мест в авиарейсе).
Инкапсуляция
Реализация абстракции должна быть скрыта от остальной части системы, т.е. инкапсулирована. Например, для хранения списка гостиниц могут применяться различные структуры данных, например, массив, коллекция или база данных. Остальная часть системы не должна знать детали представления списка.
Класс объединяет все объекты с одинаковыми структурой и поведением. Класс дает возможность создавать новые однотипные объекты. Каждый объект является экземпляром какого-то класса. Процесс создания объекта данного класса называется созданием экземпляра. Классы могут быть связаны между собой отношениями наследования и включения.
Наследование
Наследование — это ключевая особенность парадигмы объектно-ориентированного программирования. В результате абстрагирования общие свойства некоторого набора классов включаются в базовый класс более высокого уровня. А в производных классах более узкой специализации, которые "наследуют" поведение базового класса, можно изменять свойства или добавлять новые. Именно наследование делает возможным повторное использование кода и позволяет расширить сферу применения имеющихся классов.
Рассмотрим базовый класс Reservable (Резервируемый объект, ресурс) и производные от него классы, Hotel (Гостиница) и Flight (Рейс). У всех резервируемых объектов есть общие свойства, например, объем. У каждого вида резервируемых объектов есть свои уникальные характеристики. Например, гостиница имеет название и находится в определенном городе, а рейс — начало и пункт назначения. На рис. 4.1 проиллюстрированы отношения между разными резервируемыми объектами.
Абстрактные классы
Некоторые классы никогда не используются для создания экземпляров, а служат только шаблонами для создания производных классов. Например, класс Reservable (Резервируемый объект, ресурс) слишком абстрактен для создания экземпляров. На практике приходится создавать экземпляры только производных от него классов, таких как Hotel (Гостиница) и Flight (Рейс). Класс, от которого нельзя создавать экземпляры, называется абстрактным. Примером такого класса может служить класс Reservable (Резервируемый объект, ресурс). Класс, от которого можно создавать экземпляры, называется конкретным.
Рис. 4.1. Отношение наследования для разных классов резервируемых объектов
Отношения между классами
Класс может находиться в следующих отношениях с другим классом:
- Отношение наследования (IS-A, является) определяет, что один из классов является частным случаем другого. Класс Hotel (Гостиница) (подкласс, или производный класс) является одним из видов класса Reservable (Резервируемый объект, ресурс) т.е. надклассом, или базовым классом.
- Отношение включения (HAS-A, имеет) определяет, что один класс (целое) состоит из других классов (частей). Объект HotelBroker (целое) включает в себя список объектов типа Hotel (Гостиница).
- Отношение использования (USES-A, использует), — более слабое отношение между классами, чем включения (HAS-A, имеет). Это отношение можно определить следующим образом. Класс X находится в отношении использования (USES-A, использует) с классом У, если при выполнении обязанностей класса А" используется класс Y, т.е. в описании класса X используется класс Y.
Давайте рассмотрим задачу создания платежной ведомости для разных категорий служащих. Зарплата для разных категорий служащих может начисляться по-разному. Например, некоторые оплачиваемые служащие получают установленный заработок. Рабочему, получающему зарплату, заработная плата начисляется в зависимости от количества отработанных часов. Коммерческому служащему платят комиссионные, которые он зарабатывает на продажах.
Традиционный подход к этой задаче состоит в том, что поле с типом сотрудника, помещается в структуру объекта "сотрудник" и обработка данных происходит с помощью оператора выбора switch, причем для каждой категории служащих предусматривается своя case-фраза. Использование оператора выбора switch часто приводит к ошибкам, — ведь нужно быть внимательным при добавлении новой категории служащих.
Можно поступить по-другому, локализовав логику расчета зарплаты в каждом из классов служащих. Нужно только реализовать для каждого класса служащих свой собственный метод GetPay. В базовом классе можно реализовать функцию GetPay, которая будет содержать общий код для разных категорий служащих, чтобы при добавлении новой категории служащих не пришлось вносить изменения в код. Опишите метод GetPay в базовом классе, и потом в каждом производном классе переопределите его, т.е. опишите метод GetPay, который будет "подменять" метод GetPay базового класса. Вызовите метод GetPay через ссылку на объект базового класса Employee (Служащий). В результате для каждого конкретного класса служащих будет вызван свой метод GetPay.
Способность одного и того же метода выполнять разные действия в зависимости от объекта, к которому он применяется, называется полиморфизмом. Полиморфизм намного уменьшает сложность программных систем и является важной составляющей парадигмы объектного программирования.
Не следует специально менять программную модель, для того чтобы ввести полиморфизм. В примере "Бюро путешествий Acme" мы имеем дело с тремя разными абстрактными базовыми классами, но в этом случае нет необходимости использовать полиморфизм для обобщения поведения. С другой стороны классы .NET Framework часто используют полиморфизм, в чем можно убедиться, прочитав главу 5 "Управляемый C++ в .NET Framework".
Бюро путешествий Acme предоставляет несколько видов услуг: резервирование гостиничного номера, авиабилета и автомобилей напрокат. На простом примере приема заказов на резервирование, который и далее используется в книге, проиллюстрируем характерные черты .NET. В этой главе будет разработана архитектура общей системы приема заказов на резервирование. Мы создадим систему гостиничных брокеров (посредников), в которой реализуются следующие функции:
- добавить гостиницу в список гостиниц;
- вывести список гостиниц;
- вывести список гостиниц, которые находятся в заданном городе;
- забронировать гостиничный номер на определенный период времени;
- вывести список резервирований;
- вывести список резервирований заданного клиента
Система будет также содержать список клиентов. При регистрации клиентов, вводятся имя и адрес электронной почты, каждому клиенту присваивается идентификационный номер. В подсистеме управления клиентами реализуются следующие функции:
- зарегистрировать клиента,
- сменить адрес электронной почты клиента;
- вывести информацию о клиенте или список всех клиентов.
В этой главе все списки гостиниц, заказов и клиентов реализуются в виде массивов В следующей главе вместо массивов будут использоваться коллекции .NET и реализуется больше возможностей, например, удаление гостиницы из списка и отмена резервирования. В последующих главах пример программы усовершенствуется будет разработан графический пользовательский интерфейс, данные будут храниться в базе данных и т. д.
Код программы примера находится в папке CaseStudy для этой главы
Поскольку на самом деле нам нужно реализовать не только систему резервирования гостиничных номеров, но и систему резервирования других ресурсов, включая резервирование авиабилетов и автомобилей напрокат, необходимо тщательно продумать механизм абстракции С одной стороны, чем больше функций мы сможем включить в базовый класс, тем меньше останется работы при реализации каждой конкретной системы резервирования. С другой стороны, если базовый класс будет содержать слишком много функций, то множество задач, для решения которых его можно применить, уменьшится При качественном проектировании необходимо определить оптимальное множество функций, включаемых в базовый класс.
Еще одним свойством "хороших" абстракций является то, что их не задевают даже серьезные изменения в реализации. Далее в этой книге будет показано, что вносить изменения в абстракции системы резервирования гостиничных номеров не придется даже тогда, когда мы реализуем ее с помощью базы данных SQL Server.
Упомянутые абстракции в C++ представлены с помощью абстрактных классов, описанных в файле Broker.срр, расположенном в папке CaseStudy для этой главы
Класс Reservabie (Резервируемый объект, ресурс)
В качестве первой абстракции выберем объект, который мы будем резервировать. Назовем эту абстракцию просто Reservabie (Резервируемый объект, ресурс). Основной момент в резервировании — это использование ресурса Нельзя, чтобы один ресурс был зарезервирован несколькими пользователями одновременно, поэтому основным атрибутом Reservabie (Резервируемый объект, ресурс) является capacity (объем имеющихся ресурсов). Например, в гостинице — 100 номеров, авиарейс имеет 250 мест Для объекта Reservable (Резервируемый объект, ресурс) также потребуется уникальный идентификатор, который мы обозначим unitid (Вместо более длинного, неудобного имени reservableid, было выбрано более короткое unitid. Позже мы будем использовать слово unit (объект, единица) и в других именах Например, метод для добавления резервируемого объекта назовем AddUnit)
В приложениях мы введем дополнительный атрибут cost (стоимость). Для гостиничного номера — это его стоимость за сутки, для авиабилета — цена и т д Обратите внимание на то, что к некоторым резервируемым объектам этот атрибут может быть неприменим Например, стоимость зала заседаний для компании может и не быть определена, но поскольку наше приложение будет использоваться в коммерческих целях, то атрибут cost (стоимость) нужно включить в модель
Упрощения
Поскольку цель примера состоит в том, чтобы проиллюстрировать основные понятия управляемого C++ и .NET, модель примера довольно упрощена, чтобы не тратить время на программирование деталей. Например, в реальной гостинице есть несколько видов номеров разной стоимости. Точно так же на авиарейс имеются места в разных классах. На практике ситуация еще осложняется тем, что цена билета зависит от того, был ли он зарезервирован, есть ли ограничения на полет и т.д. Чтобы облегчить себе жизнь, предположим, что стоимость всех резервируемых объектов одного типа одинакова.
В управляемом C++ мы представим Reservable (Резервируемый объект, ресурс) в виде абстрактного класса.
public _gc _abstract class Reservable
// сборщик мусора - класс Reservable
{
static private int nextid = 0; // статический частный
protected: // защищенный
int unitid; public:
int capacity; // вместимость
Decimal cost; // Десятичная стоимость;
Reservable(int capacity, Decimal cost)
// вместимость, Десятичная стоимость
{
this->capacity = capacity;
// вместимость
this->cost = cost;
// стоимость
unitid = nextid++;
}
};
В конструкторе можно задать атрибуты capacity (объем имеющихся ресурсов) и cost (стоимость) Значение поля unitid генерируется автоматически с помощью статической переменной. Минимальное значение этого атрибута — ноль, так как мы будем использовать его в качестве одного из индексов двумерного массива, который содержит информацию о количестве клиентов, резервирующих данный объект на конкретную дату.
Роль спецификаторов управления доступом private (частный), internal (внутренний) и protected (защищенный) мы обсудим позже
Резервирование
Ксиди клиент резервирует объект, создается запись о резервировании. Класс Reser-v,u j on (Резервирование) содержит информацию о резервировании.
public _gc _abstract class Reservation
// сборщик мусора - абстрактный класс Резервирование
{
public:
int Reservationld;
int Unitld;
DateTime Date; // Дата
int NumberDays;
static private int nextReservationld = 1; // статический
// частный
Reservation() // Резервирование
{
Reservationld = nextReservation!d++;
}
};
Значение поля Reservationld генерируется автоматически. Поле Unitld идентифицирует зарезервированный объект. Поле Date (Дата) определяет начальную дату резервирования, поле NumberDays определяет количество дней, на которые зарезервирован объект.
Брокер
Третья абстракция, класс Broker (Брокер), — это модель брокера, который отвечает за резервирование разнообразных объектов. Данная абстракция реализована в виде абстрактного класса. Этот класс содержит список резервируемых объектов, представленных в виде массива units, и список резервирований, представленных в виде массива reservations (резервирования). С помощью двумерного массива numCost отслеживается количество клиентов, которые зарезервировали объект на указанный день.
public _gc _abstract class Broker // сборщик мусора - абстрактный класс Брокера ' -{
private: // частный
int MaxDay;
static const int MAXRESERVATION = 10; // статическая константа
static int nextReservation = 0;
static int nextUnit = 0;
int numCust [,];
protected: // защищенный
Reservation *reservations[]; // Резервирование
Reservable *units[];
public:
Broker(int MaxDay, int MaxUnit) // Брокер
{
this->MaxDay = MaxDay;
numCust = new int _gc [MaxDay, MaxUnit];
units = new Reservable*[MaxUnit];
reservations = new Reservation*[MAXRESERVATION];
// резервирование }
Структура ReservationResult
Для возвращения результата резервирования используется простая структура ReservationResult.
public _gc struct ReservationResult
// сборщик мусора - struct ReservationResult
{
public:
int Reservationld;
Decimal ReservationCost; // Десятичное число ReservationCost
Decimal Rate; // Десятичное число - цена
String *Comment;
};
Поле Rate (Цена) определяет стоимость резервируемого объекта за один день. Значение поля ReservationCost определяет полную стоимость резервирования объекта, которая равняется количеству дней, умноженному на стоимость резервирования за один день. Если возникают проблемы, в качестве Reservationld возвращается -1. причем в поле Comment (Примечание) описывается проблема. Структура создана таким образом, что информация о результате будет передаваться в любом случае, например, ее можно использовать в Web-службах, которые не поддерживают обработку исключений.
Базовый класс Broker (Брокер) не только является абстрактным брокером, который резервирует объекты, но также содержит общую логику регистрации резервирований и список резервирований. Если реализовать эту логику в абстрактном (базовом) классе, то это сделает его еще более полезным, так как значительно упростит реализацию резервирования в производных классах.
Метод Reserve (Резерв)
Основным методом класса Broker (Брокер) является метод Reserve (Резерв).
ReservationResult *Reserve(Reservation *res) // Резервирование
{
int unitid = res->Unit!d;
DateTime dt = res->Date; // Дата
int numDays = res->NumberDays;
ReservationResult *result = new ReservationResult;
// Проверить, находятся ли даты
// в пределах поддерживаемого диапазона
int day = dt.DayOfYear - 1;
if (day + numDays > MaxDay) // если (день + numDays> MaxDay)
{
result->Reservation!d = -1; // результат
result->Comment = "Dates out of range";
// результат-> Комментарий = "Даты вне диапазона";
return result; // результат }
// Проверить, доступны ли комнаты для всех дат
for (int i = day; i < day + numDays; i++)
{
if (numCust[i, unitid] >= units[unitid]->capacity)
// вместимость
{
result->Reservation!d = -1; // результат
result->Coiranent = "Room not available";
// результат-> Комментарий = "Комната не доступна";
return result; // результат
}
}
// Резервировать комнату для требуемых дат
for (int i = day; i < day + numDays; i++) n
umCust[i, unitid] += 1;
// Добавить резервирование (Add reservation)
// к списку резервирования,
// возвратить результат
AddReservation(res);
result->Reservation!d = res->Reservation!d; // результат
result->ReservationCost = // результат
units[unitid]->cost * numDays; // стоимость
result->Rate = units[unitid]->cost; // результат = стоимость;
result->Comment = "OK"; // результата Комментарий = "хорошо";
return result;
}
Метод Reserve (Резерв) разработан так, чтобы с его помощью можно было зарезервировать разные типы резервируемых объектов. Таким образом, объект Reservation (Резервирование), который хранится в списке резервируемых объектов, создается в классе, производном от Broker (Брокер), и передается в метод Reserve (Резерв) в качестве параметра. Например, объект HotelBroker резервирует объект HotelReservation и т.д. Потом создается объект ReservationResult, который и будет возвращен (поля Unitid, Date (Дата) и NumberDays наследуются из базового класса Reservation (Резервирование)).
ReservationResult *Reserve(Reservation *res) // Резервирование
{
int unitid = res->UnitId;
DateTime dt = res->Date; // Дата
int numDays = res->NumberDays;
ReservationResult *result = new ReservationResult;
Затем мы проверяем, попадают ли даты резервирования в определенный период времени, который поддерживается системой (чтобы облегчить задачу, возьмем период в один год). Для этой цели используем структуру DateTime из пространства имен System (Система). Если какая-то дата не входит в указанный период, будет выдано сообщение об ошибке.
// Проверить, лежат ли даты в пределах поддерживаемого диапазона
int day = dt.DayOfYear - 1;
if (day + numDays > MaxDay) // если (день + numDays> MaxDay)
{
result->Reservation!d = -1; // результат
result->Comment = "Dates out of range";
// результат-> Комментарий = "Даты вне диапазона";
return result; // результат }
Потом нужно для каждого из запрашиваемых дней проверить, доступен ли этот ресурс, то есть, не будет ли число резервирований превышать объем ресурса (гостиницы). Мы сделаем это с помощью массива numCust. Первый индекс этого двумерного массива определяет конкретную дату, а второй — идентификационный код резервируемого объекта (Обратите внимание на то, что поля и методы названы именами, которые описывают их суть, например, HotelBroker).
// Проверить, доступны ли комнаты для всех дат
for (int i = day; i < day + numDays; i++)
{
if (numCust[i, unitid] >= units[unitid]->capacity)
// вместимость
{
result->Reservation!d = -1; // результат
result->Comment = "Room not available";
// результат-> Комментарий = "Комната не доступна";
return result; // результат
}
}
Далее нужно зарезервировать номер на указанный период времени, увеличив количество клиентов в numCust на единицу в каждый из дней, на которые клиент делает запрос.
// Резервировать комнату для требуемых дат
for (int i = day; i < day + numDays; i++)
numCust[i, unitid] += 1;
Наконец, мы добавляем резервирование к списку резервирований и возвращаем результат.
// Добавить резервирование к списку резервирований
// и возвратить результат
AddReservation(res);
result->Reservation!d = res->Reservation!d; // результат
result->ReservationCost = // результат
units[unitid]->cost * numDays; // стоимость
result->Rate = units[unitid]->cost;
// результат = стоимость;
result->Comment = "OK";
// результат-> Комментарий = "хорошо";
return result;
}
Список резервирований и резервируемых объектов
Класс Broker (Брокер) также содержит список резервирований и резервируемых объектов. Для нашей реализации в виде массивов мы реализуем только методы добавления (Add), а в последующих версиях примера рассмотрим, как удалять элементы из списка.
void AddReservation(Reservation *res) // Резервирование
{
reservations[nextReservation++] = res; // резервирование
}
void AddUnit(Reservabie *unit)
{
units[nextUnit++] = unit;
}
В данной реализации класса Broker (Брокер) все списки представлены в виде массивов. Поскольку такая реализация может не быть (и на самом деле не будет) сохранена в последующих версиях, мы не станем рассматривать функции работы с массивами или обращаться к элементам с помощью индексов. Мы реализуем общедоступные свойства NumberUnits и NumberReservations, чтобы обеспечить доступ для просмотра частных переменных nextUnit и nextReservat ion.
_property int get_NumberUnits()
{
return nextUnit;
}
_property int get_NumberReservations()
{
return nextReservation;
}
Представление простых полей Reservationld, Unit Id, Date (Дата) и NumberDays класса Reservation (Резервирование) не меняется, следовательно, мы не будем инкапсулировать эти поля. Потом, если потребуется, мы можем реализовать некоторые из этих полей как свойства, не изменяя при этом коа клиента. Однако в данный момент, и в дальнейшем, мы просто будем использовать общедоступные поля.
public _gc _abstract class Reservation
// сборщик мусора - абстрактный класс Резервирование
{
public:
int Reservationld;
int Unitld;
DateTirie Date; // Дата
int NumberDays;
static private int nextReservationld = 1;
Reservation() // Резервирование
{
Reservationld = nextReservation!d++;
}
};
В управляемом C++ поддерживается модель единичного наследования. Следовательно, класс может быть порожден не больше чем из одного базового класса. Такая модель проста и дает возможность избежать усложнений и неопределенностей, которые возникают при множественном наследовании в неуправляемом C++. Хотя класс в управляемом C++ может быть производным только от одного базового класса, однако он может быть наследником нескольких интерфейсов, — это мы обсудим в следующей главе. В данном разделе мы рассмотрим наследование в связи с примером резервирования гостиничного номера.
Если вы используете механизм наследования, учитывайте все абстракции вашей объектной модели и те из них, которые можно будет повторно использовать, поместите в базовые классы как можно более высокого уровня. Добавлять и изменять свойства можно в более специализированных производных классах, которые наследуют стандартное поведение базовых. Благодаря наследованию облегчается повторное использование кода и его расширяемость. Кроме того, производный класс может содержать более подходящий для членов базового класса интерфейс.
Давайте рассмотрим базовый класс Reservable (Резервируемый объект, ресурс) и производный от него класс Hotel (Гостиница). У всех резервируемых объектов есть некоторые общие свойства, например, идентификатор объекта (ID), объем (capacity) и стоимость (cost). У каждого отдельного вида резервируемых объектов есть свои уникальные свойства. Например, в классе гостиницы есть атрибуты City (Город) и Hotel-Name (Название гостиницы).
Синтаксис наследования в управляемом C++
Наследование в управляемом C++ реализуется таким образом: в операторе class производного класса через двоеточие указывается имя базового класса. В файле HotelBroker.h в папке CaseStudy показано, как из базового класса Reservable (Резервируемый объект, ресурс) порождается класс Hotel (Гостиница).
public _gc class Hotel : public Reservable
// класс сборщика мусора - Гостиница:Reservable
{
};
Класс Hotel (Гостиница) автоматически наследует все поля класса Reservable (Резервируемый объект, ресурс) и содержит свои собственные поля City (Город) и Но-telName (Название гостиницы).
Внесение изменений в интерфейс существующих членов класса
Члены базового класса, unit id, capacity (объем имеющихся ресурсов), cost (стоимость), предназначены для внутреннего использования и не должны быть общедоступными. В классе Hotel (Гостиница) находятся общедоступные члены Hotel Id, Num-berRooms и Rate (Цена), к которым пользователи имеют доступ "только для чтения". Когда мы реализуем свойство таким образом, можно вместо абстрактного имени, например capacity (объем имеющихся ресурсов), которое используется в базовом классе, выбрать имя, более конкретное, например, NumberRooms.
Вызов конструкторов базового класса
Если в производном классе есть конструктор с параметрами, возможно, вы захотите передать некоторые из этих параметров в конструктор базового класса. В C++ можно вызвать конструктор базового класса, поставив перед ним двоеточие. В управляемом C++, в отличие от обычного неуправляемого C++, можно использовать только один базовый класс и список параметров, так как здесь поддерживается только единичное наследование.
Hotel( // Гостиница
String *city,
String *name,
int number, // число
Decimal cost) // Десятичная стоимость
: Reservable(number, cost) // число, стоимость
{
City = city;
// Город = город;
HotelName = name; // название
}
Обратите внимание на то, что в управляемом C++ можно вызвать только конструктор базового класса, из которого непосредственно был порожден данный класс. Нельзя вызвать конструктор, стоящий в иерархии наследования выше.
С помощью абстрактных классов Reservable (Резервируемый объект, ресурс), Reservation (Резервирование) и Broker (Брокер) можно легко реализовать систему резервирования конкретного ресурса, например гостиничного номера. На рис. 4.2 показана иерархия наследования: класс Hotel (Гостиница) является производным от класса Reservable (Резервируемый объект, ресурс), класс HotelReservation — производным от класса Reservation (Резервирование), класс HotelBroker — производным от класса Broker (Брокер).
В этом разделе мы рассмотрим основные моменты реализации примера, которая находится в папке Case Study для этой главы.
Рис. 4.2. Иерархия классов для системы резервирования "Бюро путешествий Acme"
Перед тем, как продолжить просмотр кода, неплохо было бы запустить пример. Программа TestBroker. exe представляет собой консольное приложение. Если после приглашения на ввод команды вы наберете "help" в командной строке, то будет выведен следующий список команд:
Enter command, quit to exit
H> help
The following commands are available:
hotels shows all hotels in a city
all shows all hotels
cities shows all cities
add adds a hotel
book book a reservation
bookings show all bookings
register register a customer
email change email address
show show customers
quit exit the program H>
Вот перевод этой выдачи:
Введите команду, quit для выхода
Н> помощь
Доступны следующие команды:
hotels (гостиницы) показывает все гостиницы в городе
all (все) показывает все гостиницы
cities (города) показывает все города
add (добавить) добавляет гостиницу
book (заказать) заказывает резервирование
bookings (заказы) показывает все заказы
register (регистрировать) регистрирует клиента
email (электронная почта) изменяет адрес электронной почты
show (показать) показывает клиентов
quit выход из программы
Н>
Поэкспериментируйте с этой программой, пока полностью не изучите ее свойства.
Класс HotelReservation— это простой класс, который является производным класса Reservation (Резервирование). Его код находится в файле hotelbroker.h. Этот класс включает в себя некоторые дополнительные общедоступные поля и свойство ArrivalDate (Дата прибытия), которое несет больше конкретного смысла, чем поле Date (Дата) базового класса.
public _gc class HotelReservation : public Reservation
// класс сборщика мусора - HotelReservation: общедоступное
Резервирование
{
public:
int Customerld;
String *HotelName;
String *City;
DateTime DepartureDate;
_property DateTime get_ArrivalDate()
{
return Date; // Дата
}
_property void set_ArrivalDate(DateTime value) // значение
{
Date = value;
// Дата = значение;
}
};
Самая важная задача в примере — реализовать класс HotelBroker, который является производным от класса Broker (Брокер). Код этого класса находится в файле hotel-broker.h.
public _gc class HotelBroker : public Broker
// класс сборщика мусора - HotelBroker: общедоступный Брокер
{
private: // частный
// статические константы;
static const int MAXDAY = 366;
static const int MAXUNIT = 10;
static const int MAXCITY = 5;
static private int nextCity = 0; // статическая частная
String *cities[];
public:
HotelBroker() : Broker(MAXDAY, MAXUNIT) // Брокер
{
cities = new String*[MAXCITY]; // города
AddHoteK"Atlanta", "Dixie", 100, 115.00); //Атланта,
// Дикси
AddHotel("Atlanta", "Marriott", 500, 70.00); // Атланта,
// Мариот
AddHotel("Boston", "Sheraton", 250, 95.00); // Бостон,
// Шератон
}
};
Для описания массивов вводятся константы, и создается массив, содержащий названия городов. Конструктор определяет массивы с помощью конструктора базового класса, инициализирует массив cities (города) и добавляет несколько гостиниц для тестирования.
Потом определяется свойство NumberCity и метод добавления гостиницы в список гостиниц.
_property int get_NumberCity()
{
return nextCity;
}
String *AddHotel(
String *city,
String *name,
int number, // число
Decimal cost) // Десятичная стоимость
{
if (Findldfcity, name) != -1)
// если (Findld (город, название)! =-1)
return "Hotel is already on the list";
// "Гостиница уже находится в списке";
Hotel *hotel = // Гостиница
new Hotel(city, name, number, cost);
// новая Гостиница (город, название, число, стоимость);
AddUnit(hotel); // гостиница
AddCity(city); // город
return "OK";
}
Частные вспомогательные функции помогают найти идентификатор гостиницы и добавить город в список. Город можно добавить только тогда, когда его в списке еще нет: список не может содержать два одинаковых города.
int Findld(String *city, String *name)
{
for (int i = 0; i < NumberUnits; i++)
{
Hotel *hotel = dynamic_cast(units[i]);
// Гостиница
if ((String::Compare(hotel->City, city) == 0)
// сравнить (гостиница-> Город, город)
&& (String::Compare(
// сравнить (гостиница-> HotelName, название)
hotel->HotelName, name) == 0))
return hotel->Hotel!d; // гостиница
}
return -1;
}
void AddCity(String *city)
{
// проверить, есть ли город уже в списке, добавить, если нет
if ('Contains(city))
// если (! Содержит (город))
cities[nextCity++] = city;
// города [nextCity ++] = город;
}
bool Contains(String *city)
{
for (int 1=0; i < NumberCity; i++)
{
if (String::Compare(cities[i] , city) == 0)
// сравниваются (города [i], город)
return true; // истина
}
return false; // ложь
}
Итак, мы реализовали методы вывода списка всех гостиниц, гостиниц определенного города и списка всех городов. Стоит взглянуть на код, чтобы понять, как реализуется простое форматирование текста.
Наконец, мы подошли к описанию ключевого в классе HotelBroker метода Reserve (Резерв), с помощью которого можно зарезервировать номер.
ReservationResult *Reserve(
int customerld, String *city, String *name,
DateTime dt, int numDays)
{
int id = Findld(city, name);
// int идентификатор = Findld (город, название{имя});
if (id == -1)
// если (идентификатор ==-1)
{
ReservationResult *result =
new ReservationResult;
result->Reservation!d = -1; // результат
result->Comment = "Hotel not found";
// результат-> Комментарий = "Гостиница не найдена";
return result; // результат
}
HotelReservation *res = new HotelReservation;
res->Unit!d = id; // идентификатор
res->CustomerId = customerld;
res->HotelName = name; // название
res->City = city; // Город = город
res->ArrivalDate = dt;
res->DepartureDate =
dc.Add(TimeSpan(numDays, 0, 0, 0)); // Добавить на период
res->NumberDays = numDays;
return Broker::Reserve(res);
}
Реализовать класс HotelBroker оказалось несложно, потому что в его основе лежит логика, реализованная в классе Broker (Брокер). Если гостиницы нет в списке гостиниц, то возвращается сообщение об ошибке. После этого создается объект Но-telReservation, который передается в качестве параметра методу Reserve (Резерв) базового класса. В производном классе мы создаем объект резервирования, так как нам нужны все поля класса HotelReservation, а не только поля, унаследованные от класса Reservation (Резервирование). Для того чтобы подсчитать дату отбытия (прибавить количество дней, на которые зарезервирован номер, к дате прибытия), мы используем структуру TimeSpan вместо ранее использовавшейся для этих целей структуры DateTime. Такое вычисление сделать нетрудно, поскольку в структуре DateTime знак операции + перегружен.
Нельзя реализовать систему резервирования, не смоделировав клиентов, которые ее используют. Класс Customers (Клиенты), который находится в файле customers.h, поддерживает список объектов типа Customer (Клиент). Этот список также представлен в виде массива. Реализация указанного класса очень похожа на реализацию гостиничных классов, поэтому она будет приведена в общих чертах, а точнее, мы приведем лишь структуры данных и объявления общедоступных методов и свойств.
//Customer.h
using namespace System;
// использовать пространство имен Система;
namespace 0I { namespace NetCpp { namespace Acme {
// пространство имен 01 {пространство имен NetCpp
// {пространство имен Acme {
public _gc class Customer
// класс сборщика мусора Клиент
{
public:
int Customerld;
String *FirstName;
String *LastName;
String *EmailAddress;
private: // частный
static int nextCustld = 1; // статический
public:
Customer(String *first, String *last, String *email) // Клиент
{
Customerld = nextCustId++;
FirstName = first;
LastName = last;
EmailAddress = email; // электронная почта
}
};
public _gc class Customers
// класс сборщика мусора Клиенты
{
private: // частный
Customer *customers []; // Клиент
static int nextCust =0; // статический
public:
Customers(int MaxCust) // Клиенты
{
customers = new Customer*[MaxCust]; // клиенты
RegisterCustomer(
"Rocket", //"Ракета"
"Squirrel", "rocky@frosbitefalls.com"); // "Белка"
RegisterCustomer(
"Bullwinkle", "Moose", "moose@wossamotta.edu");
// "Американский лось"
}
_property int get_NumberCustomers()
int RegisterCustomer(
String *firstName,
String *lastName,
String *emailAddress)
void Add(Customer *cust) // Добавить (Клиент)
void ShowCustomers(int customerId)
void ChangeEmailAddress(
int id, String *emailAddress) // идентификатор
};
Код примера полностью находится в пространстве имен 01::NetCpp: -.Acme. Все . файлы с описанием классов начинаются с директивы namespace (пространство имен). В файле TestHotel.h помешена соответствующая директива using. Определяется пространство имен 01: : NetCpp: : Acme следующим образом:
namespace OI { namespace NetCpp { namespace Acme {
// пространство имен OI {пространство имен NetCpp
// {пространство имен Acme {
}}}
Класс TestHotel, который находится в файле TestHotel .h, содержит интерактивную программу для испытания классов, связанных с резервированием гостиницы, и классов клиентов, поддерживающих описанные ранее команды. В этом классе имеется цикл, просматривающий команды, — такой цикл считывает команду и затем выполняет ее. Класс содержит большой блок try для всех команд, за которым следует обработчик исключений catch. Обратите внимание, — чтобы получить доступ к пространству имен нужно использовать директиву using.
//TestHotel.h
using namespace System;
// использовать пространство имен Система;
using namespace 0I::NetCpp::Acme;
// использовать пространство имен OI::NetCpp::Acme;
public _gc class TestHotel
// класс сборщика мусора TestHotel
{
public:
static void Main()
{
const int MAXCUST = 10; // константа
HotelBroker *hotelBroker = new HotelBroker;
Customers *customers = new Customers(MAXCUST) ;
// новые Клиенты
InputWrapper *iw = new InputWrapper;
String *cmd;
Console::WriteLine("Enter command, quit to exit");
// ("Введите команду, quit для выхода");
cmd = iw->getString("H> ");
while (! cmd->Equals("quit"))
{
try // попытка
{
if (cmd->Equals("hotels")) // если Равняется
// ("гостиницы")
{
String *city = iw->getString("city: ");
// город
hotelBroker->ShowHotels(city); // город
}
else if (cmd->Equals("all")) // если Равняется
// ("все")
hotelBroker->ShowHotels ();
else
hotelhelp() ;
}
catch (Exception *e) // Исключение
{
Console::WriteLine(
"Exception: {0}", e->Message);
// "Исключение: {0} ", e-> Сообщение);
}
cmd = iw->getString("H> ");
}
}
};
В этой главе сделан обзор принципов объектно-ориентированного программирования на управляемом C++, причем много внимания было уделено изучению наследования. Мы обратились к примеру "Бюро путешествий Acme", который продолжим использовать на протяжении всей книги. Мы также рассмотрели абстракции, наиболее подходящие для того, чтобы реализовать системы резервирования разных объектов, и реализовали систему резервирования гостиничных номеров. Описанные нами абстрактные базовые классы можно использовать и для реализации других систем резервирования.