Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Metodicheskie_ukazania_k_vypolneniyu_KP_OOP.doc
Скачиваний:
1
Добавлен:
01.07.2025
Размер:
3 Мб
Скачать
☆

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

Результатами объектно-ориентированного проектирования являются диаграммы, выполненные в нотациях Booch или UML. В нотации Booch к фазе проектирования относятся – диаграммы классов, объектов, состояний и переходов, взаимодействия [2]; а в нотации UML – диаграммы классов, объектов, последовательностей, кооперации, состояний, деятельности [3].

5.2.1 Диаграммы классов

Диаграмма классов показывает классы и их отношения, тем самым представляя логический аспект проекта и передавая структуру классов, формирующих архитектуру системы [2].

Два главных элемента диаграммы классов – это классы и их основные отношения. На рис. 5.2 показано обозначение для представления класса на диаграмме. Класс обычно представляют аморфным объектом, вроде облака, также допускается обозначение в виде прямоугольника со скругленными краями. Каждый класс должен иметь уникальное имя.

Рисунок 5.2 – Значок класса

На обозначениях классов полезно перечислять несколько свойств (атрибутов/полей) и методов (операций/функций-членов) класса. Атрибут обозначает часть составного объекта, или агрегата. Атрибуты используются для выражения отдельных свойств класса. В нотации Booch используется синтаксис, в котором свойство может обозначаться именем или типом данных, или и тем и другим, и, возможно, иметь значение по умолчанию:

    • A - только имя;

    • :C - только тип данных (класс);

    • A:C - имя и тип данных (класс);

    • A:C=E - имя, тип данных (класс) и значение по умолчанию.

Имя свойства должно быть недвусмысленно в контексте класса. Метод – это услуга, предоставляемая классом. Методы обычно изображаются внутри значка класса только своим именем. Чтобы отличать их от свойств, к их именам добавляются скобки. Иногда полезно указать полную сигнатуру метода:

    • N() - только имя метода;

    • RN(Аргументы) – тип возвращаемого значения (R), имя и формальные параметры (если есть).

Имена методов должны пониматься в контексте класса однозначно в соответствии с правилами перегрузки методов.

Пример. Обозначение класса на диаграмме классов.

На рис. 5.3 показан пример обозначения класса электроприбора ElectricalAppliance. Имена его свойств и методов взяты из ОО словаря ПрО (табл. 5.2). Свойство state имеет логический тип данных bool (включен прибор или нет) и по умолчанию установлено в значение false (выключен). Вертикальная черта слева от свойства означает, что оно имеет модификатор доступа protected, т.е. свойство защищено от доступа извне. Для доступа к свойству принято применять пару методов: для чтения – геттер GetState() и для записи – сеттер SetState().

Рисунок 5.3 – Пример обозначения класса

В подобной организации логики работы со свойствами заключается принцип инкапсуляции – класс защищает свое состояние, но предоставляет открытый, контролируемый изнутри интерфейс для работы с ним.

В случае класса ElectricalAppliance сеттер отсутствует, что говорит о том, что внешние сущности могут лишь наблюдать за состоянием электроприбора, но никак не могут изменить его состояние по своей воле. Вместо этого класс предоставляет свой интерфейс в виде двух методов: включение On() и выключение Off(), т.о. любая внешняя сущность может управлять состоянием электроприбора путем его включения и выключения. Причем, перед тем, как произвести воздействие на электроприбор, можно оценить его текущее состояние, иначе было бы нелогично пытаться включить уже включенный прибор или выключить выключенный.

Метод Imitate() предназначен для имитирования поведения любого класса объектов в данный момент времени. Его наличие обусловлено в большей степени использованием проектируемых классов в имитационном моделировании динамических систем и процессов. Если бы у нас во всей глобальной иерархии наследования был один суперкласс, например Object, то с очень большой вероятностью у него был бы виртуальный метод Imitate().

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

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

Пример. Составление чернового протокола класса.

Используя ОО словарь ПрО (табл. 5.2), CRC-карточку класса (табл. 5.10) и графическое обозначение класса (рис. 5.3) на диаграмме классов составим приблизительный протокол класса электроприбора ElectricalAppliance.

На данном этапе мы просто переносим имя класса, имена свойств и методов на язык С++ максимально абстрагируясь от типов данных свойств, параметров и результатов методов. По возможности сохраняя все же известные модификаторы доступа, типы данных и начальные значения.

Полученный протокол класса представлен ниже.

class ElectricalAppliance // класс электроприбор

{

protected: // защищенные члены класса

bool state; // состояние

public: // открытые члены класса

ElectricalAppliance(); // конструктор по умолчанию

~ElectricalAppliance(); // деструктор

bool GetState(); // геттер для состояния

void On(); // включить

void Off(); // выключить

void Imitate(); // имитировать работу в течении еденицы времени

};

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

Как было отмечено выше, классы состоят в отношениях между собой. Виды отношений между классами показаны на рис. 5.4: ассоциация, наследование, агрегация (has) и использование [2]. При изображении конкретной связи ей можно сопоставить текстовую метку, документирующую имя этой связи или подсказывающую ее роль. Имя связи не обязано быть глобальным, но должно быть уникально в своем контексте.

Значок ассоциации соединяет два класса и означает наличие семантической связи между ними. Ассоциации часто отмечаются существительными, например Employment (место работы), описывающими природу связи. Класс может иметь ассоциацию с самим собой (так называемая рефлексивная ассоциация). Одна пара классов может иметь более одной ассоциативной связи. Возле значка ассоциации можно указать ее мощность, используя синтаксис следующих примеров:

  • 1 - в точности одна связь;

  • N - неограниченное число (0 или больше);

  • 0..N - ноль или больше;

  • 1..N - одна или больше;

  • 0..1 - ноль или одна;

  • 3..7 - указанный интервал;

  • 1..3, 7 - указанный интервал или точное число.

Рисунок 5.4 – Значки отношений между классами

Обозначение мощности пишется у конца линии ассоциации и означает число связей между каждым экземпляром класса в начале линии с экземплярами класса в ее конце. Если мощность явно не указана, то подразумевается, что она не определена.

Обозначения оставшихся трех типов связи уточняют рисунок ассоциации дополнительными пометками. Это удобно, так как в процессе разработки проекта связи имеют тенденцию уточняться. Сначала отражается семантическая связь между двумя классами, а потом, после принятия тактических решений об истинных их отношениях, эта связь уточняется как наследование, агрегация или использование.

Значок наследования, представляющего отношение "общее/частное", выглядит как значок ассоциации со стрелкой, которая указывает от подкласса к суперклассу. Подкласс наследует структуру и поведение своего суперкласса. Класс может иметь один (одиночное наследование), или несколько (множественное наследование) суперклассов. Как правило, циклы в наследовании запрещаются. К наследованию значок мощности не приписывается.

Пример. Обозначение на диаграмме и реализация на языке C++ отношения наследования.

На рис. 5.5 показан фрагмент иерархии наследования в ПрО «Микроволновая печь». Обратите внимание на изменившееся обозначения класса ElectricalAppliance. Треугольник с буквой «А» обозначает, что класс является абстрактным, т.е. невозможно создание его экземпляров. Кроме того метод Imitate() объявлен как virtual Imitate() = 0, т.е. является чисто виртуальной функцией, а значит, все дочерние классы от ElectricalAppliance обязаны реализовывать (перегружать) данный метод. Эта возможность обеспечивается принципом полиморфизма.

Рисунок 5.5 – Обозначение отношения наследования

На рисунке классы Button и MicrowaveOven наследуют класс ElectricalAppliance, что в принципе логично – и кнопка и микроволновая печь являются электроприборами. Значит, они наследуют состояние и поведение своего базового класса, т.е. их тоже можно включать, выключать, проверять их состояние. В этом и состоит принцип наследования.

Так как обозначение класса ElectricalAppliance на рис. 5.5 модифицировано по сравнению с рис. 5.3, то и протокол класса будет изменен.

class ElectricalAppliance abstract // класс электроприбор - абстрактный класс

{

protected: // защищенные члены класса

bool state; // состояние

public: // открытые члены класса

ElectricalAppliance(); // конструктор по умолчанию

~ElectricalAppliance(); // деструктор

bool GetState(); // геттер для состояния

void On(); // включить

void Off(); // выключить

virtual void Imitate() = 0; // имитировать работу в течении еденицы времени

// чисто виртуальная функция

};

Протокол класса Button будет выглядеть следующим образом. Через двоеточие после имени класса указывается суперкласс с модификатором наследования, который определяет область видимости членов суперкласса из дочернего класса и через объект дочернего класса. Также в обязательном порядке переопределен метод Imitate().

class Button : // класс кнопка

public ElectricalAppliance // наследует электроприбор

{

public:

Button();

~Button();

void Push(); // нажать

void Imitate(); // перегруженный метод имитации работы

};

Значок агрегации обозначает отношение "целое/часть" (связь "has") и получается из значка ассоциации добавлением закрашенного кружка на конце, обозначающем агрегат. Экземпляры класса на другом конце стрелки будут в каком-то смысле частями экземпляров класса-агрегата. Разрешается рефлексивная и циклическая агрегация. Агрегация не требует обязательного физического включения части в целое.

Пример. Обозначение на диаграмме и реализация на языке C++ отношения агрегации.

На рис. 5.6 показан пример обозначения отношения агрегации между двумя классами MicrowaveOven и Button. При этом MicrowaveOven является агрегатом по отношению к Button, и у MicrowaveOven может быть единственный экземпляр Button, а отдельный Button может принадлежать единственному MicrowaveOven.

Рисунок 5.6 – Обозначение отношения агрегации

Протокол класса MicrowaveOven будет выглядеть следующим образом. В качестве одного из открытых свойств объявлен экземпляр типа Button.

class MicrowaveOven : // класс микроволновая печь

public ElectricalAppliance // наследует электроприбор

{

public: // открытые члены класса

Button button; // кнопка

MicrowaveOven();

~MicrowaveOven();

void Imitate(); // имитировать работу в течении еденицы времени

};

На рис. 5.6 на конце агрегации, обозначающем часть агрегата, добавлен квадрат. Квадрат отвечает за физическое включение частей в агрегат: закрашенный – агрегация по значению, незакрашенный – агрегация по ссылке. В примере используется агрегация по значению, и поэтому в протоколе класса MicrowaveOven жестко определен объект Button. Если бы использовалась агрегация по ссылке, то был бы определен указатель на Button. Применение агрегации по значению целесообразно, когда часть постоянно принадлежит агрегату, а по ссылке – когда в процессе выполнения программы необходимо осуществлять замену частей агрегата.

class MicrowaveOven : // класс микроволновая печь

public ElectricalAppliance // наследует электроприбор

{

public: // открытые члены класса

Button* button; // указатель на кнопку

MicrowaveOven();

~MicrowaveOven();

void Imitate(); // имитировать работу в течении еденицы времени

};

Знак использования обозначает отношение "клиент/сервер" и изображается как ассоциация с пустым кружком на конце, соответствующем клиенту. Эта связь означает, что клиент нуждается в услугах сервера, то есть методы класса-клиента вызывают методы класса-сервера или имеют сигнатуру, в которой возвращаемое значение или аргументы принадлежат классу сервера.

Пример. Обозначение на диаграмме и реализация на языке C++ отношения использования.

На рис. 5.7 показан пример обозначения отношения использования между классами Man и MicrowaveOven, Button. Класс человека Man использует класс печи MicrowaveOven для приготовления блюд, что отражено в сигнатуре метода Imitate(MicrovavrOven&). А где-то в реализации этого метода человек взаимодействует с открытым свойством печи – кнопкой Button, вызывая у нее метод нажать Push().

Рисунок 5.7 – Обозначение отношения использования

Протокол класса будет выглядеть следующим образом.

class Man // класс человек

{

protected: // защищенные члены класса

int timeToAction; // время до взаимодействия с печью

public: // открытые члены класса

Man(); // конструктор

~Man(); // детсруктор

void Imitate( // имитировать деятельность в течении еденицы времени

MicrowaveOven&); // используется печь

};

Пример. Диаграмма классов.

На рис. 5.8 приведена диаграмма классов.

На диаграмме показаны все классы, полученные на этапе ОО анализа и представленные в ОО словаре ПрО. Человек Man взаимодействует с печью MicrowaveOven через ее открытые свойства – дверь door (может открывать open() и закрывать close()) и кнопку button (может нажимать push()). Для генерации случайных чисел человек Man использует статический класс Random, обозначенный облаком с тенью. Random предоставляет интерфейс для генерации случайных чисел распределенных по равномерному закону Uniform() с указанием левой min и правой max границы отрезка и нормальному закону Normal() с указанием математического ожидания M и дисперсии D.

Рисунок 5.8 – Диаграмма классов

Печь MicrowaveOven наследует два базовых абстрактных класса: электроприбор ElectricalAppliance и бытовой прибор HouseholdAppliance. Печь состоит из двери door, кнопки button, спикера speaker, силового элемента powerElement, лампы lamp и таймера timer. На обозначении отношений агрегации защищенных частей печи MicrowaveOven дополнительно нанесена черта, обозначающая защищенность данной части агрегата.