1 Объектное проектирование (по Г.Бучу)
Основные методы проектирования
Существует небольшое число общеизвестных методов, использующихся в том числе в качестве основных при проектировании ПО:
- Абстракция – идеализация модели сложных объектов путем игнорирования не слишком важных деталей
- Декомпозиция – разбиение системы на все меньшие и меньшие взаимодействующие части, каждая из которых совершенствуется независимо
- Иерархия – расположение кода и данных по уровням
В течение нескольких десятков лет существования компьютеров было разработано множество подходов к решению задач декомпозиции, абстракции и иерархии
1.1 Абстракция
Абстракция выделяет существенные характеристики некоторого объекта, отличающие его от всех других видов объектов и, таким образом, четко определяет концептуальные границы с точки зрения наблюдателя.
Шоу: “Абстракция – упрощенное описание системы, при котором одни детали выделяются, а другие опускаются. Хорошей является абстракция, которая выделяет существенные для рассмотрения и использования детали, а несущественные опускает.”
Принцип наименьшего удивления: абстракция должна охватывать все поведение объекта, но не больше и не меньше, не привносить сюрпризов или побочных эффектов, лежащих вне ее сферы применимости.
Главная задача ОО проектирования – выбор правильного набора абстракций для заданной предметной области.
1.2 Инкапсуляция
В то время как абстракция описывает внешний интерфейс класса, инкапсуляция занимается его внутренним устройством.
Принцип разделения интерфейса и реализации: в интерфейсной части собрано все, что касается взаимодействия данного объекта с любыми другими объектами; реализация скрывает детали, не имеющие отношения к этому процессу взаимодействия объектов.
Интерфейс объекта не должен зависеть от реализации.
Инкапсуляция – это процесс отделения друг от друга элементов объекта, определяющих его устройство и поведение.
Инкапсуляция служит для изоляции контрактных обязательств абстракции от их реализации.
1.3 Модульность
- Цель разделения программы на модули – разделить задачу на несколько менее сложных подзадач, которые можно решать независимо.
- Б.Лискоу: ''Модульность – это разделение программы на фрагменты, которые компилируются по отдельности, но могут устанавливать связи с другими модулями''.
- Парнас: ''связи между модулями – это их представление друг о друге''.
В C++ существуют следующие виды модулей:
- модули как самостоятельная концепция (раздельно компилируемые файлы)
- свободные функции и операторы, в т.ч. перегруженные
- классы
- шаблоны классов и функций
Интерфейсная часть модулей помещается в отдельные файлы с расширением .h (.hpp, .hxx).
Иногда реализацию inline-функций помещают в отдельные файлы с расширением .ixx.
Правильное разделение на модули является почти такой же сложной задачей, как и выбор правильного набора абстракций.
Для большинства программ лучшим решением будет сгруппировать логически связанные классы и объекты по отдельным модулям.
В больших системах иногда применяются также другие способы размещения кода по модулям исходя из критерия минимизации времени компиляции.
Парнас:
- конечная цель декомпозиции программы на модули - снижение затрат на программирование за счет независимой разработки и тестирования
- структура модуля должна быть достаточно простой для восприятия
- реализация каждого модуля не должна зависеть от реализации других модулей
- должны быть приняты меры для облегчения процесса внесения изменений там, где они наиболее вероятны
Перекомпиляция тела модуля редко является трудоемкой операцией. Перекомпиляция интерфейсной части более трудоемка.
Поэтому интерфейсная часть модулей должна быть наиболее узкой. Лучше скрыть все, что только возможно, и постепенно переносить описания из реализации в интерфейсную часть, чем «вычищать» избыточный интерфейсный код.
Правила (Парнас, Клеменс и Вейс):
- особенности системы, подверженные изменениям, следует скрывать в отдельных модулях
- в качестве межмодульных можно использовать только те элементы, вероятность изменения которых мала
- все структуры данных должны быть обособлены в модуле
- доступ к данным модуля будет возможен из всех процедур этого модуля и закрыт для всех других
- доступ к данным из модуля должен осуществляться только через процедуры данного модуля
Т.о. следует стремиться строить модули так, чтобы объединить логически связанные абстракции и минимизировать взаимные связи между модулями
IAMGE[0.50][modul]
1.4 Иерархия
Обычно число абстракций в системе намного превышает умственные способности. Инкапсуляции и модульности оказывается недостаточно.
Значительное упрощение в понимании сложных задач достигается за счет образования из абстракций иерархической структуры.
Иерархия – это упорядочение абстракций, расположение их по уровням.
В ОО программировании основными видами сложных систем являются структура классов (is-a, наследование) и структура объектов (part of, агрегация).
Типизация
Типизация – это способ защититься от использования объектов одного типа вместо другого (или управлять таким использованием).
C++ является строго типизированным языком. Необходимо максимально использовать его возможности проверки типов.
Аналог: разъемы спроектированы так, чтобы части соединялись только правильным образом. Немало изобретательности потрачено на то, чтобы разные части аппаратуры не вставлялись одна в другую
Недостаток: даже небольшие изменения в интерфейсе класса требуют перекомпиляции всех его подклассов.
Иерархия
1.5 Типизация
Точно определенные и строго типизированные интерфейсы являются фундаментальным инструментом проектирования.
Типизация позволяет находить некоторые ошибки уже на этапе компиляции. Она тем полезнее, чем больше проект.
Строго типизированный интерфейс гарантирует, что компилируются и компонуются только совместимые части программ: эти части могут делать сильные предположения относительно друг друга, при этом выполнение этих предположений гарантируется системой типов
эффект от этого проявляется в минимизации тестирования программы, что повышает эффективность и значительно упрощает фазу интеграции в проекте, над которым работало множество людей.
1.6 Повторное использование
Повторное использование предполагает использование уже написанного кода для построения других приложений, т.о. уменьшая время разработки и увеличивая качество этих приложений.
Конечная цель повторного использования – серийное производство ПО путем стыковки готовых компонентов.
Технические сложности повторного использования:
- обычно необходимо использовать почти весь код и немного переделать
- один и тот же код не может охватить все возможные ситуации (особенно не известные на момент разработки)
- используется фактически не сам код, а схема кода
2 Объекты и отношения
Введение
Объект моделирует часть окружающей действительности:
- осязаемый и/или видимый предмет
- нечто, воcпринимаемое мышлением
Объект проявляет четко выделяемое поведение.
Смит и Токи: ''Объект представляет собой конкретный опознаваемый предмет, единицу или сущность (реальную или абстрактную), имеющую четко определенное функциональное назначение в данной предметной области''
Более общее определение: Объект – это нечто, имеющее четко очерченные границы.
Примеры объектов
- завод по изготовлению композитных материалов для велосипедных рам
- заводы разделяются на цеха: механический, химический и т.д.
- цеха подразделяются на участки
- на каждом из участков установлено несколько единиц оборудования (штампы, прессы, станки)
- емкости с исходными материалами, из которых с помощью химических процессов создаются блоки композитных материалов
- конечный продукт – рамы
Каждый осязаемый предмет может рассматриваться как объект:
- токарный станок имеет четко очерченные границы, которые отделяют его от обрабатываемого на данном станке композитного блока
- рама велосипеда имеет четкие границы по отношению к участку с оборудованием
Химический процесс – это тоже объект:
- имеет четкую концептуальную границу
- взаимодействует с другими объектами посредством набора операций,
- проявляет хорошо определенное поведение
т.о. определены явные концептуальные границы, но объект неосязаем
Сфера и куб имеют обычно нерегулярное пересечение. Линия пересечения является объектом, хотя не существует отдельно от сферы и куба.
Объекты могут быть осязаемыми, но иметь размытые физические границы: реки, туман, толпы людей.
Определение объекта
Объект обладает состоянием, поведением и идентичностью.
Структура и поведение схожих объектов определяет общий для них класс.
Термины экземпляр класса и объект взаимозаменяемы.
2.1 Состояние объекта
Состояние объекта характеризуется перечнем (обычно статическим) всех свойств данного объекта и текущими (обычно динамическими) значениями каждого из этих свойств.
Примеры состояния:
- уличный автомат для торговли напитками
- текущая сумма денег
- количество имеющихся в наличии напитков
- лифт
- самообучающийся робот
Тот факт, что объект имеет состояние, означает, что он занимает некоторое место в памяти компьютера.
Регистрационные записи о сотрудниках
Пример
struct PersonnelRecord
{
char name[100];
int socialSecurityNumber;
char department[10];
float salary;
};
Описание определяет не объект, а класс, т.к. он не определяет конкретных экземпляров.
Чтобы определить экземпляры, нужно написать следующее:
PersonnelRecord deb, dave, karen, jim, torn, denise,
kaitlyn, krista, elyse;
В памяти эти объекты не пересекаются и занимают каждый свое место.
На практике принято ограничивать доступ к состоянию объекта, а не делать его общедоступным. Более предпочтительное определение:
Пример
class PersonnelRecord
{
protected:
char name[100];
int socialSecurityNumber;
char department[10];
float salary;
public:
char* employeeName() const;
int employeeSocialSecurityNumber() const;
char* employeeDepartment() const;
};
2.2 Поведение объекта
Объекты не существуют изолированно, а подвергаются воздействию или сами воздействуют на другие объекты.
Поведение – это то, как объект действует и реагирует. Поведение выражается в терминах состояния объекта и передачи сообщений.
Поведение объекта - это его наблюдаемая и проверяемая извне деятельность.
Операция – это определенное воздействие одного объекта на другой с целью вызвать соответствующую реакцию.
Пример: объект-очередь содержит следующие операции:
- append и pop – чтобы добавить и изъять элемент
- length – позволяет определить размер очереди (но не может изменить это значение)
В C++ операции над объектами называются методами или функциями-членами.
Поведение объекта задается типом операции и текущим состоянием объекта.
Пример: очередь
class Queue {
public:
Queue();
Queue(const Queue&);
~Queue();
Queue& operator=(const Queue&);
bool operator==(const Queue&) const;
bool operator!=(const Queue&) const;
void clear();
void append(const void*);
void pop();
void remove(int at);
int length() const;
int isEmpty() const;
const void* front() const;
int location(const void*);
protected:
...
};
В данном примере используется идиома ссылки на данные неопределенного типа void* (обычная для языка Си).
В очередь можно вставлять объекты произвольного типа.
Такая техника не безопасна:
- клиент должен ясно понимать, с каким объектом (какого класса) он имеет дело
- при использовании void* очередь не владеет объектами, которые в нее помещены; деструктор ~Queue() не уничтожает участников очереди
Определение Queue задает класс, а не объект. Мы должны объявить экземпляры класса, с которыми могут работать клиенты:
Queue a, b, c, d;
Операция – это услуга, которую класс может предоставить своим клиентам.
Наиболее распространенные типы операций:
- модификатор - операция, которая изменяет состояние объекта (clear, append, pop, remove)
- селектор - операция, считывающая состояние объекта, но не меняющая состояния
- итератор - операция, позволяющая организовать доступ ко всем частям объекта в строго определенной последовательности
- конструктор - операция создания объекта и/или его инициализации
- деструктор - операция, освобождающая состояние объекта и/или разрушающая сам объект
Свободные подпрограммы - это глобальные функции, не принадлежащие какому-либо классу.
Свободные процедуры группируются в соответствии с классами, для которых они создаются. Поэтому их еще называют утилитами класса.
Пример: копирование содержимого очереди в другую до тех пор, пока не встретится заданный объект (операция более высокого уровня, строится на операциях-примитивах класса Queue):
void copyUntilFound(Queue& from, Queue& to, void* item)
{
while ((!from.isEmpty()) && (from.front() != item))
{
to.append(from.front());
from.pop();
}
}
Все логически связанные свободные подпрограммы иногда собирают вместе и объявляют статическими методами в некотором классе, не имеющем состояния.
2.3 Идентичность
Идентичность объекта
Идентичность – это такое свойство объекта, которое отличает его от всех других объектов.
Источником множества ошибок в ОО программировании является неумение отличать имя объекта от самого объекта.
Примеры:
- для различения временных объектов их обычно именуют, тем самым путая адресуемость и идентичность
- в БД постоянные объекты различают по ключевому атрибуту, тем самым смешивая идентичность и значение данных
2.4 Отношения
Сами по себе объекты не представляют никакого интереса: только в процессе их взаимодействия реализуется система.
ОО программа – это «сообщество хорошо воспитанных объектов, которые вежливо просят друг друга об услугах» (Ингалс).
Пример: самолет – это «совокупность элементов, каждый из которых по своей природе стремится упасть на землю, но за счет совместных усилий преодолевающих эту тенденцию» (Галл).
Отношения любых двух объектов основываются на предположениях, которыми один обладает относительно другого: об операциях, которые можно выполнять, и об ожидаемом поведении.
Два типа отношений:
- связи
- агрегация
2.5 Связи
Связь – это физическое или концептуальное соединение между объектами.
Участвуя в связи, объект может выполнять одну из следующих трех ролей:
- Актер - объект может воздействовать на другие объекты, но сам никогда не подвергается воздействию других объектов.
- Сервер - объект может только подвергаться воздействию со стороны других объектов, но никогда не выступает в роли воздействующего объекта.
- Агент - объект может выступать как в активной, так и в пассивной роли.
Связь между объектом-клиентом и объектом-сервером необходима для того, чтобы объект-клиент мог запросить услугу у объекта-сервера.
Связь может быть односторонней или двусторонней.
Пример: Во многих производственных процессах требуется непрерывно поднять температуру до заданного значения, выдержать определенное время и понизить до нормы.
Профиль изменения температуры у разных процессов разный:
- зеркало телескопа надо охлаждать очень медленно
- закаляемую сталь надо охлаждать очень быстро
Опишем класс, управляющий процессом нагрева.
Пусть есть два объекта A и B и связь между ними. Чтобы A мог послать сообщение в B, нужно, чтобы B был видим для A.
На стадии анализа об этом можно не беспокоиться, но на стадии реализации мы должны обеспечить видимость связанных объектов
Четыре способа обеспечить видимость:
- сервер глобален по отношению к клиенту
- ссылка на сервер передана клиенту в качестве параметра операции
- сервер является частью клиента
- между клиентом и сервером установлена ассоциация
- сервер локально порождается клиентом в ходе выполнения какой-то операции
Какой из этих способов выбрать – зависит от тактики проектирования.
2.6 Агрегация
Агрегация описывает отношения целого и его части, приводящие к соответствующей иерархии объектов.
Идя от целого (агрегата), мы можем прийти к его частям (атрибутам). Но, зная указатель на часть, нельзя определить содержащий его объект (если только сведения о нем не являются частью состояния части).
Агрегация может означать физическое вхождение одного объекта в другой (самолет содержит крылья, двигатель и т.д.) или концептуальное (акционер владеет своими акциями, но они не входят в него).
Агрегация иногда предпочтительнее связи, т.к. позволяет скрыть части в целом. Связи иногда предпочтительнее, т.к. они слабее и менее ограничительны. Принимая решение, надо взвесить все.