"Гради Буч. Объектно-ориентированный анализ и проектирование с примерами приложений на С++ (fb2) " - читать интересную книгу автора (Буч Гради)

5.4. Диаграммы объектов

Существенное: объекты и их отношения

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

Рис. 5-23. Значок объекта.

Объекты. На рис. 5-23 показан значок, который изображает объект на диаграмме объектов. Как и в диаграммах классов, можно провести горизонтальную линию, разделяющую текст внутри значка объекта на две части: имя объекта и его атрибуты.

Имя объекта следует синтаксису для атрибутов, и может быть либо записано в одной из трех следующих форм:

• A - только имя объекта

• :C - только класс объектов

• A:C - имя объекта и класса

либо использовать синтаксис выбранного языка реализации. Если текст не умещается внутри значка, то следует или увеличить значок, или сократить текст. Если несколько значков объектов на одной диаграмме используют одно и то же неквалифицированное имя (то есть имя без указания класса), то они означают один и тот же объект. В противном случае каждый значок означает отдельный объект [На одной диаграмме могут присутствовать значки объектов с одинаковыми неквалифицированными именами, но относящиеся к разным классам, в том случае, если эти классы имеют общего предшественника. Это позволяет представить распространение операций от подкласса к суперклассу и наоборот]. Если на разных диаграммах есть объекты с одинаковыми неквалифицированными именами, то это разные объекты, если только их имена не квалифицированы явно.

Смысл неквалифицированных имен зависит от контекста диаграммы объектов. Более точно: диаграммы объектов, определенные на самом верхнем уровне системы, имеют глобальную область видимости; другие диаграммы объектов могут быть определены для категорий классов, отдельных классов или отдельных методов, а, значит, иметь соответствующие области видимости. Квалифицированное имя может быть использовано при необходимости явной ссылки на глобальные объекты, переменные классов (статические элементы в C++), параметры методов, атрибуты или локально определенные объекты в той же области видимости.

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

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

Рис. 5-24. Значок связи между объектами.

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

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

Отношения между объектами. Как говорилось в главе 3, объекты взаимодействуют с другими объектами через связи, обозначение которых показано на рис. 5-24. Подобно тому, как объект является экземпляром класса, связь является экземпляром ассоциации.

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

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

В установившемся состоянии структуры классов и объектов системы должны быть согласованы. Если мы показываем на диаграмме операцию M на классе B, вызванную по связи L, то м должна быть объявлена в спецификации B, или в спецификациях его суперклассов.

Как показано на рис. 5-24, рядом с соответствующей связью на диаграмме можно записать набор сообщений. Каждое сообщение состоит из следующих трех элементов:

• D - символ синхронизации, обозначающий направление вызова

• M - вызов операции или извещение о событии

• S - необязательный порядковый номер.

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

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

• N() - только имя операции

• RN(arguments) - возвращаемое значение, имя и фактические параметры операции.

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

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

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

Пример. На рис. 5-25 показана диаграмма объектов для нашего тепличного хозяйства в контексте категории классов Planning (планирование; описана на рис. 5-7). Цель этой диаграммы - проиллюстрировать сценарий выполнения обычной функции системы, а именно, прогнозирование затрат на сбор урожая некоторого посева.

Выполнение этой функции требует сотрудничества нескольких различных объектов. Сценарий начинается с вызова объектом PlanAnalyst (анализатор планов) операции timeToHarvest() (время собирать урожай) над утилитой класса PlanMetrics (параметры планов). При этом объект с передается как фактический параметр операции. Затем утилита PlanMetrics вызывает операцию status() (состояние) на некотором неименованном объекте класса GardeningPlan (план выращивания). В пояснении говорится: "Надо проверить, что этот план действительно выполняется". В свою очередь, объект GardeningPlan вызывает операцию maturationTime() (время созревания) на выбранном объекте класса GrainCrop (посев зерновых), запрашивающую ожидаемое время созревания посева. Когда эта операция-селектор будет выполнена, управление возвращается объекту класса PlanAnalyst, который затем непосредственно вызывает операцию C.yield(), унаследованную от суперкласса (операция Crop::yield()). Управление снова возвращается объекту класса PlanAnalyst, который продолжает сценарий, выполняя над собой операцию netCost().  

Рис. 5-25. Диаграмма объектов гидропонной системы.

На диаграмме показана связь между объектами классов PlanAnalyst и GardeningPlan. Хотя сообщения между ними не посылаются, связь отражает существование семантической зависимости между этими объектами.

Дополнительные понятия

То, что мы описали, составляет существенные элементы диаграммы объектов. Однако многие запутанные вопросы разработки требуют некоторого расширения используемых обозначений. Мы предупреждали при описании диаграмм классов и хотим подчеркнуть опять: дополнительные понятия должны использоваться только при необходимости.

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

Рис. 5-26 дает пример использования этого дополнительного обозначения. Здесь мы видим, что некоторый объект класса PlanAnalyst заносит информацию об определенном посеве (Crop) в анонимный объект CropEncyclopedia (энциклопедия посевов) и делает это, пока находится в роли Автор.

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

Поток данных. Как было описано в главе 3, данные могут передаваться по или против направления посылки сообщения. Иногда явное указание направления передачи данных помогает объяснить семантику конкретного сценария. Мы используем для этого значок, заимствованный из обозначений структурного проектирования. На рис. 5-26 дан пример его использования: здесь показано, что после завершения сообщения insert (вставить) возвращается значение succeeded (успех). Передаваться и возвращаться может либо объект, либо значение.  

Рис. 5-26. Роли.

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

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

Например, канал связи от объекта PlanAnalyst к утилите классов PlanMetrics помечен буквой G; это значит, что утилита класса глобальна. Объект C по-разному виден объекту PlanAnalyst и объекту GardeningPlan: с точки зрения первого объект с класса GrainCrop виден как параметр некоторой операции (обозначается буквой P); с точки зрения второго C виден как атрибут или поле, то есть как часть агрегированного объекта (обозначен буквой F (field)).

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

• G - сервер глобален для клиента

• P - сервер является параметром некоторой операции клиента

• F - сервер является частью клиента

• L - сервер локально определен в области видимости клиента.

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

Рис. 5-27. Значки видимости.

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

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

Ранее, говоря о дополнительных элементах спецификаций класса, мы заметили, что есть четыре типа семантики: последовательная, защищенная, синхронизированная и активная. По существу, все объекты класса наследуют соответствующую семантику класса; все объекты считаются последовательными, если явно не указано обратное. Мы можем явно показать многозадачную семантику объекта на диаграмме объектов, указав в левом нижнем углу значка объекта одно из слов sequential, guarded, synchronous или active. Например, на рис. 5-28 мы видим, что объекты H, C и некий экземпляр класса EnvironmentalController - активные. Немаркированные объекты, такие как L, считаются последовательными.

Символ синхронизации сообщений, введенный ранее (простая стрелка), представляет обычную последовательную передачу сообщения. Однако, при наличии нескольких потоков управления мы должны указывать и другие формы синхронизации. Пример на рис. 5-28, возможно, несколько надуманный, иллюстрирует различные типы синхронизации сообщений, которые могут появиться на диаграмме объектов. Сообщение turnOn() (включить) - пример простой посылки сообщения; оно изображается простой стрелкой. Семантика простой посылки сообщения гарантирована только в однопоточной среде. Остальные сообщения из этого примера используют некоторые формы синхронизации процессов. Все такие дополнительные виды синхронизации применяются только к серверам, которые не являются последовательными.  

Рис. 5-28. Активные объекты и синхронизация.

Например, сообщение startUp() - синхронизированное, то есть клиент будет ждать до тех пор, пока сервер не примет сообщение. Посылка синхронизированного сообщения эквивалентна механизму свиданий задач в языке Ada (rendezvous). В случае сообщения isReady() клиент отложит сообщение, если сервер не сможет его немедленно обработать. Сообщение restart() будет отложено клиентом, если сервер не может его обработать за указанный промежуток времени.

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

Расписание. В программах, имеющих ограничения по времени, важно отслеживать чистое время с момента начала каждого сценария. Для обозначения относи тельного времени (в секундах) мы ставим знак плюс. Например, на рис. 5-29 сообщение startUp() вызывается в первый раз спустя 5 секунд после начала сценария далее, через 6.5 секунд после начала сценария следует сообщение ready() и затем, спустя 7 секунд после начала сценария, - сообщение turnOn().

Спецификации

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

Context: глобальный | категория | класс | операция

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

Рис. 5-29. Расписание.