Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
УБП _Пособие.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
16.5 Mб
Скачать

Операции

Операции реализуют связанное с классом поведение. Операция включает три части - имя, параметры и тип возвращаемого значения. Параметры - это аргументы, получаемые операцией "на входе". Тип возвращаемого значения относится к результату действия операции.

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

В языке UML операции имеют следующую нотацию:

Имя Операции (аргумент! : тип данных аргумента!, аргумент2 : тип данных аргумента!,...) : тип возвращаемого значения

Операции определяют ответственности классов. При идентификации операций и анализе классов имейте в виду следующее:

  • Относитесь с подозрением к любому классу, имеющему только одну или две операции. Возможно, класс написан совершенно правильно, но, может быть, его надо объединить с каким-нибудь другим классом.

  • С большим подозрением относитесь к классу без операций. Как правило, класс инкапсулирует не только данные, но и поведение. Класс без поведения может быть лучше промоделирован как один или несколько атрибутов.

  • Кроме того, с подозрением относитесь к классу со слишком большим числом операций. Набор ответственностей класса должен быть управляем. Если он слишком большой, то им будет трудно управлять. В такой ситуации часто бывает лучше разделить класс па два меньших.

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

Следует рассмотреть четыре различных типа операций.

Операции реализации

Операции реализации (implementor operations) реализуют некоторые бизнес-функции. Такие операции можно найти, исследуя диаграммы взаимодействия. Диаграммы этого типа фокусируются на бизнес-функциях, и каждое сообщение диаграммы, скорее всего, можно соотнести с операцией реализации.

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

Операции управления

Операции управления (manager operations) управляют созданием и уничтожением объектов. В эту категорию попадают конструкторы и деструкторы классов.

Операции доступа

Атрибуты обычно бывают закрытыми или защищенными. Тем не менее, другие классы иногда должны просматривать или изменять их значения. Для этого существуют операции доступа (access operations).

Пусть, например, у нас имеется атрибут Salary класса Employee. Мы не хотим, чтобы все остальные классы могли изменять этот атрибут. Вместо этого к классу Employee мы добавляем две операции доступа - GetSalary и SetSalary. К первой из них, являющейся общей, могут обращаться и другие классы. Она просто получает значение атрибута Salary и возвращает его вызвавшему ее классу. Операция SetSalary также является общей, она помогает вызвавшему ее классу установить новое значение атрибута Salary. Эта операция может содержать любые правила и условия проверки, которые необходимо выполнить, чтобы зарплата могла быть изменена.

Такой подход дает возможность безопасно инкапсулировать атрибуты внутри класса, защитив их от других классов, но все же позволяет осуществить к ним контролируемый доступ. Создание операций Get и Set (получения и изменения значения) для каждого атрибута класса является стандартом.

Вспомогательные операции

Вспомогательными (helper operations) называются такие операции класса, которые необходимы ему для выполнения его ответственностей, но о которых другие классы не должны ничего знать. Это закрытые и защищенные операции класса.

Как и операции реализации, вспомогательные операции можно обнаружить на диаграммах последовательности и кооперативных диаграммах. Часто такие операции являются рефлексивными сообщениями на этих диаграммах.

Чтобы идентифицировать операции, выполните следующие действия:

  1. Изучите диаграммы последовательности и кооперативные диаграммы. Большая часть сообщений на этих диаграммах является операциями реализации. Рефлексивные сообщения будут вспомогательными операциями.

  2. Рассмотрите управляющие операции. Может потребоваться добавить конструкторы и деструкторы.

  3. Рассмотрите операции доступа. Для каждого атрибута класса, с которым должны будут работать другие классы, надо создать операции Get и Set.

Связи

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

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

Ассоциации

Ассоциация (association) - это семантическая связь между классами. Их рисуют на диаграмме классов в виде обыкновенной линии.

После того, как классы связали ассоциацией, они могут передавать друг другу сообщения на диаграмме последовательности или кооперативной диаграмме. Ассоциации могут быть двунаправленными, как в примере, или однонаправленными. На языке UML двунаправленные ассоциации рисуют в виде простой линии без стрелок или со стрелками с обеих ее сторон. На однонаправленной ассоциации изображают только одну стрелку, показывающую ее направление.

После определения ассоциации в классы помещаются соответствующие дополнительные атрибуты. Например, если между классом Дом и классом Жилец установлена связь ассоциации, класс Дом получит атрибут "жилец", чтобы он мог знать, кто его владелец, а класс Жилец - атрибут "дом, чтобы он мог определить, каким домом он владеет.

Ассоциация дает классу возможность знать об общих атрибутах и операциях другого класса. Например, в случае двунаправленной ассоциации между классами Дом и Жилец класс Жилец знает об общих атрибутах и операциях Дома, а Дом - о Жильце. Таким образом, на диаграммах последовательности Дом может досылать сообщения Жильцу и наоборот.

Ассоциации можно сделать и однонаправленными. Такие связи легче создавать, ими легче управлять, и с их помощью можно найти классы, допускающие повторное использование. Допустим, что ассоциация направлена от Жильца к Дому. В этом случае Жилец знает об общих атрибутах и операциях Дома, но Дом не знает об атрибутах и операциях Жильца. Сообщения на диаграммах последовательности и кооперативных диаграммах могут отправляться Жильцом и приниматься Домом, но не наоборот.

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

Однонаправленные связи позволяют определить классы, являющиеся кандидатами на повторное использование. Если ассоциация двунаправлена, каждый класс-участник должен знать о другом, и потому ни один из них не может использоваться без другого. Допустим, однако, что имеется однонаправленная ассоциация между Жильцом и Домом. Жилец должен знать о Доме, и потому не может использоваться без него. Однако, Дом не должен знать о Жильце и потому допускает самостоятельное повторное использование. Класс с большим количеством исходящих однонаправленных связей повторно использовать трудно, а класс, все связи которого входящие, чрезвычайно легко.

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

Зависимости

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

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

При генерации кода для этих классов к ним не будут добавляться новые атрибуты.

Однако, будут созданы специфические для языка операторы, необходимые для поддержки связи. Например, на языке C++ в код войдут необходимые операторы

#include.

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

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

Хотя мы и находимся сейчас на очень детальном уровне модели, решение, принятое относительно подходов, может повлиять на всю модель целиком. Может потребоваться добавить новый аргумент к операции класса Жилец, например, +BuyHouse (theHouse : House).

Второе отличие ассоциаций от зависимостей связано с направлением этих связей. Ассоциации могут быть двунаправленными, но зависимости всегда однонаправлены.

Зависимости можно рисовать не только между классами, но и между пакетами. Фактически, это единственный тип связей, существующий между пакетами. Зависимость между пакетами, как и между классами, рисуют пунктирной линией.

Связь зависимости между пакетами А и В означает, что некоторый класс пакета А связан однонаправленной связью с некоторым классом пакета В. Иначе говоря, класс А должен знать что-либо о классе В.

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

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

Создавая зависимости между пакетами, старайтесь по мере возможности избегать циклических зависимостей. Такая зависимость предполагает, что класс из пакета А должен знать о классе из пакета В, а еще какой-то класс пакета В должен знать о классе из пакета А. Это означает, что ни один пакет нельзя самостоятельно использовать повторно, и изменения в одном из них неизбежно повлияют на другой. Таким образом, теряется одно из преимуществ пакетов - они становятся слишком взаимозависимы. Чтобы разбить циклическую зависимость, разделите пакет на два. В нашем примере можно взять классы пакета В, от которых зависит А, и переместить их в третий пакет С. Теперь зависимости пакетов будут выглядеть следующим образом:

Агрегации

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

В дополнение к простой агрегации UML вводит более сильную разновидность агрегации, называемую композицией. Согласно композиции, объект-часть может принадлежать только единственному целому, и, кроме того, как правило, жизненный цикл частей совпадает с циклом целого: они живут и умирают вместе с ним. Любое удаление целого распространяется на его части.

Такое каскадное удаление нередко рассматривается как часть определения агрегации, однако оно всегда подразумевается в том случае, когда множественность роли составляет 1..1; например, если необходимо удалить Клиента, то это удаление должно распространиться и на Заказы (и, в свою очередь, на Строки заказа).

Один класс может участвовать в нескольких отношениях агрегации с другими классами. Например, класс Машина образует такие связи со всеми ее частями: Дверь, Мотор и Покрышка.

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

При генерации кода для агрегации автоматически будут созданы поддерживающие ее дополнительные атрибуты. В примере автомобиля класс-целое Машина получит атрибуты, соответствующие классам Дверь, Мотор и Покрышка и всем остальным частям связи агрегации.

Обобщения

С помощью обобщений (generalization) показывают связи наследования между двумя классами. Большинство объектно-ориентированных языков непосредственно поддерживают концепцию наследования. Она позволяет одному классу наследовать все атрибуты, операции и связи другого. На языке UML связи наследования называют обобщениями и изображают в виде стрелок от класса-потомка к классу-предку:

В этом примере описаны два типа сотрудников: с почасовой оплатой и получающих оклад. Оба этих типа наследуют от класса Служащий. Класс Служащий в таком случае называется суперклассом (superclass), а оба производных от него класса -подклассами (subclass). Стрелка показывает направление от подкласса к суперклассу.

Общие для обоих типов элементы размещаются в классе Служащий. Таким образом, классы Почасовик и Штатник наследуют от него атрибуты ФИО, Адрес, ИНН и операции Найм и Увольнение.

Помимо наследуемых, каждый подкласс имеет свои собственные уникальные атрибуты, операции и связи. Класс сотрудников с почасовой оплатой имеет уникальный атрибут Оплата за час, а класс сотрудников на окладе имеет уникальный атрибут Оклад.

Связи обобщения позволяют сэкономить время и усилия как при разработке, так и при дальнейшей поддержке программы. В приведенном примере вам не требуется писать и поддерживать две различные копии операции Найм - для каждого подкласса. Достаточно разработать только одну копию. Любые сделанные в ней изменения будут автоматически наследоваться классами Почасовик, Штатник и всеми остальными подклассами суперкласса Служащий.

Определяя обобщение на диаграмме, можно развертывать структуру наследования сверху вниз или снизу вверх. В первом случае необходимо найти классы, у которых имеется несколько различных разновидностей. Например, изучив класс Служащий, вы поймете, что существуют различные типы сотрудников. Если вы строите структуру снизу вверх, ищите классы, имеющие между собой что-то общее. В нашем примере, рассмотрев классы Почасовик и Штатник, вы поймете, что они похожи друг на друга. После этого можно уже создать новый класс Служащий для хранения общих элементов.

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

Множественность

Множественность (multiplicity) показывает, сколько экземпляров одного класса взаимодействуют с помощью этой связи с одним экземпляром другого класса в данный момент времени.

Например, при разработке системы регистрации курсов в университете можно определить классы Курс и Студент. Между ними установлена связь: у курсов могут быть студенты, а у студентов - курсы. Вопрос, на который должен ответить параметр множественности: "Сколько курсов студент может посещать в данный момент?" и "Сколько студентов может за раз посещать один курс?"

Так как множественность дает ответ на оба эти вопроса, ее индикаторы устанавливаются на обоих концах линии связи. В примере регистрации курсов мы решили, что один студент может посещать от 0 до четырех курсов, а один курс могут слушать от 10 до 20 студентов. На диаграмме классов это можно изобразить следующим образом:

Цифры 0..4 обозначают, что один студент может посещать от нуля до четырех курсов, а цифры 10..20 - что на одном курсе могут заниматься от 10 до 20 студентов.

В языке UML приняты следующие соглашения для обозначения множественности:

Множественность

Значение

*

Много

0

Ноль

1

Один

0..*

Ноль или больше

1..*

Один или больше

0..1

Ноль или один

1..1

Ровно один

Можно также ввести собственное значение множественности в одном из следующих форматов:

Формат

Значение

<число>

ровно <число>

<число 1>..<число2>

Между <числом 1> и <числом 2>

<число>..n

<число> или больше

<число 1>, <число 2>

<число 1> или <число 2>

<число 1>, <число 2>..<число 3>

ровно <число 1> или между <числом 2> и <числом 3>

<число 1>..<число 2>, ..<число 3>..<число 4>

между <числом 1> и <числом 2> или между <числом 3> и <числом 4>

Значение множественности позволяет понять, является ли данная связь обязательной. В рассматриваемом примере студент может посещать от нуля до четырех курсов за семестр. Это означает, что студент может вообще пропустить семестр. Если бы множественность была 1..4, то каждый студент был бы обязан за это время прослушать хотя бы один курс. Таким образом, множественность реализует бизнес-правило "каждый студент обязан за семестр прослушать хотя бы один курс11.

Как правило, для форм, экранов и окон значение множественности равно 0..1 с каждой стороны связи. Такое значение показывает, что формы могут существовать независимо одна от другой, хотя это и не всегда бывает правильно.

Имена связей

Связи можно еще уточнить с помощью имен связей или ролевых имен. Имя связи -это обычно глагол или глагольная фраза, описывающая, зачем она нужна. Например, между классом Человек и классом Компания может существовать ассоциация. Можно задать в связи с этим вопрос, а зачем она нужна. Является ли объект класса Person клиентом компании, ее сотрудником или владельцем? Чтобы определить это, ассоциацию можно назвать Нанимает:

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

Роли

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

Классы ассоциаций

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

Чтобы решить проблему, можно создать класс Ассоциаций. В этот класс следует поместить атрибут Курс, относящийся в большей степени к связи между курсом и студентом, чем к какому-то классу конкретно. Нотация UML для класса ассоциаций представлена ниже:

Выявление связей

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

  1. Начните с изучения диаграмм Последовательности и Кооперативных диаграмм. Если на них класс А посылает сообщение классу Б, между этими классами должна быть установлена связь. Как правило, обнаруживаемые таким способом связи - это ассоциации и зависимости.

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

  3. Исследуйте классы на предмет связей обобщения. Постарайтесь найти все классы, у которых может быть несколько типов или вариантов. Например, у вас может быть класс Сотрудник. В вашей компании имеется два типа сотрудников - получающих почасовую оплату и оклад. Это значит, что вам надо создать классы Почасовик и Штатник, наследующие от класса Employee. Общие для всех сотрудников атрибуты, операции и связи следует поместить в класс Сотрудник. Атрибуты, операции и связи, уникальные для сотрудников какого-то типа, надо поместить в классы Почасовик и Штатник.

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

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