Диаграммы взаимодействия (interaction diagrams) описывают взаимодействие групп объектов в различных условиях их поведения. UML определяет диаграммы взаимодействия нескольких типов, из которых наиболее употребительными являются диаграммы последовательности.
Обычно диаграмма последовательности описывает один сценарий. На диаграмме показаны экземпляры объектов и сообщения, которыми обмениваются объекты в рамках одного прецедента (use case).
Для того чтобы начать обсуждение, рассмотрим простой сценарий. Предположим, что у нас есть заказ, и мы собираемся вызвать команду для определения его стоимости. При этом объекту заказа (Order) необходимо просмотреть все позиции заказа (Line Items) и определить их цены, основанные на правилах построения цены продукции в строке заказа (Order Line). Проделав это для всех позиций заказа, объект заказа должен вычислить общую скидку, которая определяется индивидуально для каждого клиента.
На рис. 4.1 приведена диаграмма, представляющая реализацию данного сценария. Диаграммы последовательности показывают взаимодействие, представляя каждого участника вместе с его линией жизни (lifeline), которая идет вертикально вниз и упорядочивает сообщения на странице; сообщения также следует читать сверху вниз.
|
|
Одно из преимуществ диаграммы последовательности заключается в том, что мне почти не придется объяснять ее нотацию. Можно видеть, что экземпляр заказа посылает строке заказа сообщения getQuantity и getProduct. Можно также видеть, как заказ применяет метод к самому себе и как этот метод посылает сообщение getDiscountlnfo экземпляру клиента.
Однако диаграмма не все показывает так хорошо. Последовательность сообщений getQuantity, getProduct, getPricingDetails и calculateBasePrice должна быть реализована для каждой строки заказа, тогда как метод calculateDiscounts вызывается лишь однажды. Такое заключение нель-
зя сделать на основе этой диаграммы, но позднее я введу дополнительное обозначение, которое поможет в этом.
В большинстве случаев можно считать участников диаграммы взаимодействия объектами, как это и было в действительности в UML 1. Но в UML 2 их роль значительно сложнее, и полное ее объяснение выходит за рамки этой книги. Поэтому я употребляю термин участники (participants), который формально не входит в спецификацию UML. В UML версии 1 участники были объектами, и поэтому их имена подчеркивались, но в UML 2 их надо показывать без подчеркивания, как я и сделал выше.
На приведенной диаграмме я именовал участников, используя стиль anOrder. В большинстве случаев это вполне приемлемо. Вот более полный синтаксис: имя: Класс, где и имя, и класс не обязательны, но если класс используется, то двоеточие должно присутствовать. (Этот стиль выдержан на рис. 4.4.)
|
|
Каждая линия жизни имеет полосу активности, которая показывает интервал активности участника при взаимодействии. Она соответствует времени нахождения в стеке одного из методов участника. В языке UML полосы активности не обязательны, но я считаю их исключи-
тельно удобными при пояснении поведения. Единственным исключением является стадия проработки дизайна, поскольку их неудобно рисовать на белых досках.
Именование бывает часто полезным для установления связей между участниками на диаграмме. Как видно на диаграмме, вызов метода getProduct возвращает aProduct, имеющего то же самое имя и, следовательно, означающего того же самого участника, aProduct, которому посылается вызов getPricingDetails. Обратите внимание, что обратной стрелкой я обозначил только этот вызов с целью показать соответствие. Многие разработчики используют возвраты для всех вызовов, но я предпочитаю применять их, только когда это дает дополнительную информацию; в противном случае они просто вносят неразбериху. Не исключено, что даже в данном случае можно было опустить возврат, не запутав читателя.
У первого сообщения нет участника, пославшего его, поскольку оно приходит из неизвестного источника. Оно называется найденным сообщением (found message).
Другой подход можно увидеть на рис. 4.2. Основная задача остается той же самой, но способ взаимодействия участников для ее решения совершенно другой. Заказ спрашивает каждую строку заказа о его собственной цене (Price). Сама строка заказа передает вычисление дальше - объекту продукта (Product); обратите внимание, как мы показываем передачу параметра. Подобным же образом для вычисления скидки объект заказа вызывает метод для клиента (Customer). Поскольку для выполнения этой задачи клиенту требуется информация от объекта заказа, то он делает повторный вызов в отношении заказа для получения этих данных.
Во-первых, на этих двух диаграммах надо обратить внимание на то, насколько ясно диаграмма последовательности показывает различия во взаимодействии участников. В этом проявляется мощь диаграмм взаимодействий. Они не очень хорошо представляют детали алгоритмов, такие как циклы или условное поведение, но делают абсолютно прозрачными вызовы между участниками и дают действительно ясную картину того, какую обработку выполняют конкретные участники.
Во-вторых, посмотрите, как четко видна разница в стиле между двумя взаимодействиями. На рис. 4.1 представлено централизованное управление (centralized control), когда один из участников в значительной степени выполняет всю обработку, а другие предоставляют данные. На рис. 4.2 изображено распределенное управление (distributed control), при котором обработка распределяется между многими участниками, каждый их которых выполняет небольшую часть алгоритма.
Оба стиля обладают преимуществами и недостатками. Большинство разработчиков, особенно новички в объектно-ориентированном программировании, чаще всего применяют централизованное управление. Во многих случаях это проще, так как вся обработка сосредоточена в одном месте; напротив, в случае распределенного управления при попытке понять программу создается ощущение погони за объектами.
Несмотря на это фанатики объектов, такие как я, предпочитают распределенное управление. Одна из главных задач хорошего проектирования заключается в локализации изменений. Данные и программный код, получающий доступ к этим данным, часто изменяются вместе. Поэтому размещение данных и обращающейся к ним программы в одном месте - первое правило объектно-ориентированного проектирования.
Кроме того, распределенное управление позволяет создать больше возможностей для применения полиморфизма, чем в случае применения условной логики. Если алгоритмы определения цены отличаются для различных типов продуктов, то механизм распределенного управления позволяет нам использовать подклассы класса продукта (Product) для обработки этих вариантов.
|
|
Вообще, объектно-ориентированный стиль предназначен для работы с большим количеством небольших объектов, обладающих множеством небольших методов, что дает широкие возможности для переопределения и изменения. Этот стиль сбивает с толку людей, применяющих длинные процедуры; действительно это изменение является сердцем смены парадигмы (paradigm shift) при объектной ориентации. Научить этому трудно. Представляется, что единственный способ действительно понять это заключается в использовании распределенного управления при работе в объектно-ориентированном окружении. Многие люди говорят, что они испытали внезапное озарение, когда поняли смысл этого стиля. В этот момент их мозг перестроился, и они начали думать, что децентрализованное управление действительно проще