Роль определяет одну сторону зависимости. В бинарной зависимости определены две роли. Имя роли однозначно определяет одну сторону зависимости. Роли дают возможность рассматривать бинарную зависимость как связь между объектом и множеством зависимых объектов: каждая роль является обозначением объекта или множества объектов, связанных зависимостью с объектом на другом конце зависимости. Имя роли можно рассматривать как производный атрибут, множеством значений которого является множество связанных с этой ролью объектов. В бинарной зависимости пара имён ролей может использоваться для идентификации этой зависимости.
На рисунке 2.11 имена начальник и сотрудник в зависимости руководит – это имена ролей; как уже было отмечено, эту зависимость удобнее назвать начальник-сотрудник. Ещё один пример имён ролей показан на рисунке 2.13.
Пользователь может быть либо владельцем, либо зарегистрированным пользователем директории; директория может содержать в себе другие директории.
Рис. 2.13. Имена ролей
Имена ролей следует обязательно указывать в тех случаях, когда зависимость устанавливается между объектами одного и того же класса (как в случаях, показанных на рисунках 2.11 и 2.13). Имена ролей должны быть уникальны, так как они используются для различения объектов, участвующих в зависимости.
Квалификатором называется некоторый атрибут, который позволяет снизить эффективную кратность зависимости. Квалификаторы применяются в зависимостях типов «один-ко-многим» или «много-ко-многим». Так, в примере, показанном на рисунке 2.14, использование квалификатора имя файла позволяет привести зависимость даёт доступ от вида, приведённого на рисунке 2.14а, к виду, показанному на рисунке 2.14б, сократив число зависимых объектов до одного. Ещё один пример использования квалификатора показан на рисунке 2.14в: использование квалификаторов и здесь позволяет сократить кратность зависимости до одного объекта. Квалификаторы указываются на схемах в прямоугольничках, пририсованных к прямоугольнику, изображающему соответствующий класс.
|
|
|
Рис. 2.14. Использование квалификаторов
Рисунок 2.14в может быть проинтерпретирован следующим образом: центральныйкомпьютер + код ATM определяют конкретную ATM (отметим, что код АTM – имя одного из атрибутов класса ATM, а не класса центральный компьютер); аналогично центральный компьютер + код банка определяют конкретный компьютер банка. Использование квалификаторов повышает точность описания семантики и наглядность описания зависимостей.
Агрегация
Агрегация – это зависимость между классом составных объектов и классами, представляющими компоненты этих объектов (отношение «целое» – «часть»). Агрегация обозначается ромбиком: на рисунке 2.15 приведён пример агрегации; этот пример интерпретируется следующим образом: документ состоит из нескольких (нуля или более) абзацев; каждый абзац состоит из нескольких (нуля или более) предложений.
Рис. 2.15. Агрегация
Наиболее важным свойством отношения агрегации является его транзитивность (если A есть часть B, а B есть часть C, то A есть часть C): так, из рисунка 2.15 можно заключить, что документ состоит из нескольких (нуля или более) предложений. Легко видеть, что отношение агрегации антисимметрично (если A есть часть B, то B не есть часть A). Отметим также, что часть свойств целого может быть перенесена и на его части, возможно, с несущественными изменениями (например, контекст каждого оператора некоторой функции совпадает с внутренним контекстом всей функции).
Дальнейшие примеры агрегации показаны на рисунке 2.16. Отметим, что обе агрегации, показанные на рисунке 2.16а, следует рассматривать не как зависимости между пятёрками классов, а как четвёрки зависимостей между парами классов. Только при таком рассмотрении можно говорить о транзитивности и антисимметричности отношения агрегации.
|
|
|
Рис. 2.16. Примеры агрегации
Обобщение и наследование
Обобщение и наследование позволяют выявить аналогии между различными классами объектов, определяют многоуровневую классификацию объектов. Так, в графических системах могут существовать классы, определяющие обрисовку различных геометрических фигур: точек, линий (прямых, дуг окружностей и кривых, определяемых сплайнами), многоугольников, кругов и т.п. (рис. 2.17).
Рис. 2.17. Обобщение (выделение суперклассов)
Обобщение позволяет выделить класс одномерные фигуры и считать классы прямая, дуга и сплайн подклассами класса одномерные фигуры, а класс одномерные фигуры – суперклассом классов прямая, дуга и сплайн.
Если при этом принять соглашение, что атрибуты и операции суперкласса действительны в каждом из его подклассов (говорят, что эти атрибуты и операции наследуются подклассами), то одинаковые атрибуты и операции классов прямая, дуга и сплайн (подклассов) могут быть вынесены в класс одномерные фигуры (суперкласс). Аналогично можно поступить и с двумерными фигурами, определив для классов многоугольник и круг суперкласс двумерная фигура. Наконец, можно определить класс фигура как суперкласс классов нульмерная фигура, одномерная фигура и двумерная фигура. Легко видеть, что отношения «подкласс – суперкласс» (обобщение) и «суперкласс – подкласс» (наследование) транзитивны, что позволяет строить классификационные деревья. При этом атрибуты и операции каждого суперкласса наследуются его подклассами всех уровней (мы как бы выносим за скобки одинаковые операции). Это значительно облегчает и сокращает описание классов.
На схемах обобщение (наследование) изображается треугольничком (рис. 2.17). Треугольничек следует ставить даже в том случае, когда суперкласс имеет всего один подкласс. Слово размерность, следующее за верхним треугольничком на рисунке 2.17, является дискриминатором.
Дискриминатор – это атрибут типа «перечисление», показывающий, по какому из свойств объектов сделано данное обобщение.
Другие примеры обобщения (наследования) показаны на рисунке 2.18 (эти примеры связаны с основным нашим примером – системой обслуживания клиентов банковским консорциумом).
|
|
Рис. 2.18. Другие примеры обобщения (наследования)
Необходимо отметить, что, как показывает опыт практического проектирования систем, следует избегать обширных многоуровневых классификаций, так как поведение подклассов низших уровней многоуровневой классификации бывает трудно понять: большая часть (а нередко и все) атрибутов и операций таких классов определена в их суперклассах различных уровней. Если количество уровней классификации стало непомерно большим, нужно слегка изменить структурирование системы. Чтобы понять, какое число уровней является непомерно большим, можно руковод-ствоваться следующими оценками: два-три уровня наследования, как правило, приемлемы всегда. Десятиуровневая классификация почти всегда неприемлема; пять-шесть уровней, как правило, достаточно для программистов и не слишком обременяет администрацию.
Обобщение и наследование широко применяются не только при анализе требований к программным системам и их предварительном проектировании, но и при их реализации.
Иногда в подклассе бывает необходимо переопределить операцию, определённую в одном из его суперклассов. Для этого операция, которая может быть получена из суперкласса в результате наследования, определяется и в подклассе; это её повторное определение «заслоняет» её определение в суперклассе, так что в подклассе применяется не унаследованная, а переопределённая в нём операция. Напомним, что каждая операция определяется своей сигнатурой; следовательно, сигнатура переопределения операции должна совпадать с сигнатурой операции из суперкласса, которая переопределяется данной операцией. Так, в примере, изображённом на рисунке 2.17, в классе круг переопределяется операция «вращение» его суперкласса «фигура» (при повороте круга его изображение не меняется, что позволяет сделать операцию вращение в классе круг пустой).
Переопределение может преследовать одну из следующих целей:
− расширение: новая операция расширяет унаследованную операцию, учитывая влияние атрибутов подкласса;
− ограничение: новая операция ограничивается выполнением лишь части действий унаследованной операции, используя специфику объектов подкласса;
− оптимизация: использование специфики объектов подкласса позволяет упростить и ускорить соответствующий метод (например, переопределяя операцию max класса IntegerSet в его подклассе SortedIntegerSet, мы можем резко упростить её, используя специфические свойства упорядоченных множеств);
− удобство.
Целесообразно придерживаться следующих семантических правил наследования:
- все операции-запросы (операции, которые используют значения атрибутов, но не изменяют их) должны наследоваться всеми подклассами;
- все операции, изменяющие значения атрибутов, должны наследоваться во всех их расширениях;
- все операции, изменяющие значения ограниченных атрибутов, или атрибутов, определяющих зависимости, должны блокироваться во всех их расширениях (например, операция размер по оси x естественна для класса эллипс, но должна быть заблокирована (заменена пустой операцией) в его подклассе круг);
- операции не следует переопределять коренным образом; все методы, реализующие одну и ту же операцию, должны осуществлять сходное преобразование атрибутов;
- унаследованные операции можно уточнять, добавляя дополнительные действия.
Следуя этим правилам, которые, к сожалению, редко поддерживаются объектно-ориентированными языками программирования, можно сделать разрабатываемую программу более понятной, легче модифицируемой, менее подверженной влиянию различных ошибок и недосмотров.
Абстрактные классы
На рисунке 2.19 рассмотрена операция подсчёта выплат для различных категорий служащих фирмы: временных служащих с почасовой оплатой труда, постоянных служащих с понедельной оплатой труда и руководящих работников фирмы с помесячной оплатой.
Каждая из категорий служащих представлена своим подклассом класса служащий, от которого они наследуют атрибут годовой доход и операцию подсчёт выплат. Но подсчёт выплат для каждой категории служащих производится по-своему, с учётом значений их собственных (неунаследованных) атрибутов; поэтому в каждом из подклассов операция подсчёт выплат переопределяется. Следовательно, в суперклассе операция подсчёт выплат может быть определена произвольным образом, так как она никогда не будет выполняться. В то же время сигнатуры всех операций подсчёт выплат в суперклассе и в подклассах должны быть одинаковыми (иначе это будут разные операции). Из сказанного следует, что в суперклассе можно задать только сигнатуру операции подсчёт выплат, это обеспечит одинаковые сигнатуры этой операции во всех подклассах. Методы, реализующие операцию подсчёт выплат, достаточно определить только в подклассах класса служащий. Суперкласс, в котором заданы только атрибуты и сигнатуры операций, но не определены методы, реализующие его операции, называется абстрактным классом. Методы, реализующие операции абстрактного класса, определяются в его подклассах, которые называются конкретными классами.
Рис. 2.19. Абстрактный класс
Абстрактный класс не может иметь объектов, так как в нём не определены операции над объектами; объекты должны принадлежать конкретным подклассам абстрактного класса. Абстрактные классы используются для спецификации интерфейсов операций (методы, реализующие эти операции, впоследствии определяются в подклассах абстрактного класса). Абстрактные классы удобны на фазе анализа требований к системе, так как они позволяют выявить аналогию в различных, на первый взгляд, операциях, определённых в анализируемой системе.