Отношения между компонентами
Вы можете организовывать компоненты, группируя их в пакеты, так же, как это делается для классов.
При организации компонентов между ними можно специфицировать отношения зависимости, обобщения, ассоциации (включая агрегирование) и реализации.
Другим случаем отношения зависимости на диаграмме компонентов является отношение программного вызова и компиляции между различными видами компонентов. Для рассмотренного фрагмента диаграммы компонентов (рис. 6.101.) наличие подобной зависимости означает, что исполнимый компонент Control.exe использует или импортирует некоторую функциональность компонента Library.dll, вызывает страницу гипертекста Home.html и файл помощи Search.hlp, а исходный текст этого исполнимого компонента хранится в файле Control.cpp. При этом характер отдельных видов зависимостей может быть отмечен дополнительно с помощью текстовых стереотипов.
Рис. 6.101. Графическое изображение отношения зависимости между компонентами
На диаграмме компонентов могут быть также представлены отношения зависимости между компонентами и реализованными в них классами. Эта информация имеет значение для обеспечения согласования логического и физического представлений модели системы. Разумеется, изменения в структуре описаний классов могут привести к изменению этой зависимости. Ниже приводится фрагмент зависимости подобного рода, когда исполнимый компонент Control.exe зависит от соответствующих классов (рис. 6.102.).
Рис. 6.102. Графическое изображение зависимости между компонентом и классами
Компоненты и классы
Во многих отношениях компоненты подобны классам. Те и другие наделены именами, могут реализовывать набор интерфейсов, вступать в отношения зависимости, обобщения и ассоциации, быть вложенными, иметь экземпляры и принимать участие во взаимодействиях. Однако между компонентами и классами есть существенные различия: классы представляют собой логические абстракции, а компоненты – физические сущности. Таким образом, компоненты могут размещаться в узлах, а классы – нет; компоненты представляют собой физическую упаковку логических сущностей и, следовательно, находятся на другом уровне абстракции; классы могут обладать атрибутами и операциями. Компоненты обладают только операциями, доступными через их интерфейсы.
Первое отличие является самым важным. При моделировании системы решение о том, что использовать – класс или компонент, – очевидно: если моделируемая сущность непосредственно размещается в узле, то это компонент, в противном случае – класс.
Второе различие предполагает существование некоторого отношения между классами и компонентами, а именно: компонент – это физическая реализация множества логических элементов, таких как классы и кооперативные диаграммы. Как показано на рис. 6.103., отношение между компонентом и классом, который он реализует, может быть явно изображено с помощью отношения зависимости. Как правило, вам не придется рассматривать такие отношения; лучше хранить их как часть спецификации компонента.
Рис. 6.103. Компоненты и классы
Третье различие подчеркивает, что интерфейсы являются мостом между компонентами и классами. В следующем разделе более подробно объясняется, что, хотя и компоненты, и классы могут реализовывать интерфейсы, в отличие от класса услуги компонента обычно доступны только через его интерфейсы.
Компоненты и интерфейсы
Интерфейс – это набор операций, которые описывают услуги, предоставляемые классом или компонентом. В общем случае интерфейс графически изображается окружностью, которая соединяется с компонентом отрезком линии без стрелок (рис. 6.104, а). При этом имя интерфейса, которое рекомендуется начинать с заглавной буквы "I", записывается рядом с окружностью. Семантически линия означает реализацию интерфейса, а наличие интерфейсов у компонента означает, что данный компонент реализует соответствующий набор интерфейсов.
Рис. 6.104. Графическое изображение интерфейсов на диаграмме компонентов
Кроме того, интерфейс на диаграмме компонентов может быть изображен в виде прямоугольника класса со стереотипом <<interface>> и секцией поддерживаемых операций (рис. 6.104, б). Как правило, этот вариант обозначения используется для представления внутренней структуры интерфейса.
Как видно из рис. 6.105., отношения между компонентом и его интерфейсами можно изобразить двумя способами. Первый, наиболее распространенный, состоит в том, что интерфейс рисуется в свернутой (elided) форме. Компонент, реализующий интерфейс, присоединяется к нему с помощью отношения свернутой реализации. Во втором случае интерфейс рисуется в развернутом виде, возможно с раскрытием операций. Реализующий его компонент присоединяется с помощью отношения полной реализации. В обоих случаях компонент, получающий доступ к услугам других компонентов через этот интерфейс, подключается к нему с помощью отношения зависимости.
Рис. 6.105. Компоненты и интерфейсы
Интерфейс, реализуемый компонентом, называется экспортируемым интерфейсом (Export interface). Это означает, что компонент через данный интерфейс предоставляет ряд услуг другим компонентам. Компонент может экспортировать много интерфейсов. Интерфейс, которым компонент пользуется, называется импортируемым (Import interface). Это означает, что компонент совместим с таким интерфейсом и зависит от него при выполнении своих функций. Компонент может импортировать различные интерфейсы, причем ему разрешается одновременно экспортировать и импортировать интерфейсы.
Конкретный интерфейс может экспортироваться одним компонентом и импортироваться другим. Если между двумя компонентами располагается интерфейс, их непосредственная взаимозависимость разрывается. Компонент, использующий данный интерфейс, будет функционировать корректно (притом безразлично, каким именно компонентом реализован интерфейс). Разумеется, компонент можно использовать в некотором контексте только тогда, когда все импортируемые им интерфейсы экспортируются какими–либо другими компонентами.
Главная задача каждого компонентно–ориентированного средства в любой операционной системе – обеспечить возможность сборки приложений из заменяемых двоичных частей. Это означает, что вы можете создать систему из компонентов, а затем развивать ее, добавляя новые компоненты или заменяя старые, – без перекомпиляции. Именно интерфейсы позволяют достичь этого. Специфицируя интерфейс, вы можете вставить в уже работающую систему любой компонент, который совместим с этим интерфейсом или предоставляет его. Систему можно расширять, подставляя компоненты, обеспечивающие новые услуги с помощью дополнительных интерфейсов, а также компоненты, способные распознать и использовать эти новые интерфейсы. Такая семантика объясняет, что стоит за определением компонентов в UML. Компонент – это физическая заменяемая часть системы, которая совместима с одними интерфейсами и реализует другие.
Во–первых, компонент имеет физическую природу. Он существует в реальном мире битов, а не в мире концепций.
Во–вторых, компонент заменяем. Вместо одного компонента можно подставить другой, если он совместим с тем же набором интерфейсов.
Обычно механизм добавления или замены компонента с целью формирования исполняемой системы прозрачен для пользователя и обеспечивается либо объектными моделями (такими, как СОМ+ или Enterprise JavaBeans), которые часто совсем не требуют внешнего вмешательства, либо инструментальными средствами, автоматизирующими этот механизм.
В–третьих, компонент – это часть системы. Компонент редко выступает в отрыве от остальных: обычно он работает совместно с другими компонентами и, стало быть, встраивается в архитектурный или технологический контекст, для которого предназначен. Компонент является логически и физически способным к сцеплению, то есть представляет собой значимый структурный и/или поведенческий фрагмент некоторой большей системы. Компонент можно повторно использовать в различных системах. Таким образом, компоненты представляют собой фундаментальные строительные блоки, из которых собираются системы. Это определение рекурсивно – система, рассматриваемая на одном уровне абстракции, может быть всего лишь компонентом на более высоком уровне.
В–четвертых, как упоминалось выше, компонент совместим с одним набором интерфейсов и реализует другой набор.
Варианты графического изображения компонентов
Поскольку компонент как элемент модели может иметь различную физическую реализацию, иногда его изображают в форме специального графического символа, иллюстрирующего конкретные особенности реализации. Строго говоря, эти дополнительные обозначения не специфицированы в нотации языка UML. Однако, удовлетворяя общим механизмам расширения языка UML, они упрощают понимание диаграммы компонентов, существенно повышая наглядность графического представления.
Для более наглядного изображения компонентов были предложены и стали общепринятыми следующие графические стереотипы:
Во–первых, стереотипы для компонентов развертывания, которые обеспечивают непосредственное выполнение системой своих функций. Такими компонентами могут быть динамически подключаемые библиотеки (рис. 6.106, а), Web–страницы на языке разметки гипертекста (рис. 6.106, б) и файлы справки (рис. 6.106, в).
Во–вторых, стереотипы для компонентов в форме рабочих продуктов. Как правило – это файлы с исходными текстами программ (рис. 6.106, г).
\
Рис. 6.106. Варианты графического изображения компонентов на диаграмме компонентов
Эти элементы иногда называют артефактами, подчеркивая при этом их законченное информационное содержание, зависящее от конкретной технологии реализации соответствующих компонентов. Более того, разработчики могут для этой цели использовать самостоятельные обозначения, поскольку в языке UML нет строгой нотации для графического представления артефактов.
Другой способ спецификации различных видов компонентов — указание текстового стереотипа компонента перед его именем. В языке UML для компонентов определены следующие стереотипы:
<<file>> (файл) – определяет наиболее общую разновидность компонента, который представляется в виде произвольного физического файла.
<<executable>> (исполнимый) – определяет разновидность компонента–файла, который является исполнимым файлом и может выполняться на компьютерной платформе.
<<document>> (документ) – определяет разновидность компонента–файла, который представляется в форме документа произвольного содержания, не являющегося исполнимым файлом или файлом с исходным текстом программы.
<<library>> (библиотека) – определяет разновидность компонента–файла, который представляется в форме динамической или статической библиотеки.
<<source>> (источник) – определяет разновидность компонента–файла, представляющего собой файл с исходным текстом программы, который после компиляции может быть преобразован в исполнимый файл.
<<table>> (таблица) – определяет разновидность компонента, который представляется в форме таблицы базы данных.
Отдельными разработчиками предлагались собственные графические стереотипы для изображения тех или иных типов компонентов, однако, за небольшим исключением они не нашли широкого применения. В свою очередь ряд инструментальных CASE–средств также содержат дополнительный набор графических стереотипов для обозначения компонентов.