На этом шаге рассмотрим внутреннюю структуру компонента в UML.
Компонент может быть реализован как единый фрагмент кода, но в больших системах желательно иметь возможность строить крупные компоненты из малых, которые используются в качестве строительных блоков. Внутренняя структура компонента содержит части, которые вкупе с соединениями между ними составляют его реализацию. Во многих случаях внутренние части могут быть экземплярами более мелких компонентов, связанных статически через порты для обеспечения необходимого поведения, без необходимости для автора модели специфицировать дополнительную логику.
Часть – это единица реализации компонента, которой присвоены имя и тип. В экземпляре компонента содержится по одному или по несколько экземпляров каждой части определенного типа. Часть имеет множественность в пределах компонента. Если эта множественность больше единицы, то в экземпляре компонента может быть ряд экземпляров компонента данного типа. Если множественность представлена не одним целым числом, то количество экземпляров части может варьироваться в разных экземплярах компонента. Экземпляр компонента создается с минимальным количеством частей (прочие при необходимости добавляются позднее). Атрибут класса – это разновидность части: он имеет тип и множественность, и у каждого экземпляра класса есть один или несколько экземпляров атрибута данного типа.
На рис. 1 показан компонент-компилятор, состоящий из частей четырех видов.
Рис.1. Части компонента
В их числе – лексический анализатор, синтаксический анализатор (parser), генератор кода и от одного до трех оптимизаторов. Более полная версия компилятора может быть сконфигурирована с разными уровнями оптимизации; в данной версии необходимый оптимизатор может выбираться во время исполнения.
Часть – это не то же самое, что класс. Каждая часть идентифицируется по ее имени так же, как в классе различаются атрибуты. Допустимо наличие нескольких частей одного и того же типа, но вы можете различать их по именам и они предположительно выполняют разные функции внутри компонента. Например, компонент Air Ticket Sales (Продажа авиабилетов) на рис. 2 может включать отдельные части Sales для постоянных и обычных клиентов; обе они работают одинаково, но первая обслуживает только привилегированных клиентов, дает больше шансов избежать очередей и предоставляет некоторые льготы. Поскольку это компоненты одинакового типа, их потребуется различать по именам. Другие два компонента типов SeatAssignment (УказаниеМест) и InventoryManagement (УправлениеСписками) не требуют имен, поскольку присутствуют в одном экземпляре внутри компонента Air Ticket Sales.
Рис.2. Части одного типа
Если части представляют собой компоненты с портами, вы можете связать их друг с другом через эти порты. Два порта могут быть подключены друг к другу, если один из них предоставляет данный интерфейс, а другой требует его. Подключение портов подразумевает, что для получения сервиса требующий порт вызовет предоставляющий. Преимущества портов и интерфейсов в том, что кроме них больше ничего не важно; если интерфейсы совместимы, то порты могут быть подключены друг к другу. Инструментальные средства способны автоматически генерировать код вызова одного компонента от другого. Также их можно подключить к другим компонентам, предоставляющим те же интерфейсы, когда таковые появятся и станут доступны. "Проводок" между двумя портами называется коннектором. В экземпляре охватывающего компонента он представляет просто ссылку (link) или временную ссылку (transient link). Простая ссылка – это экземпляр обычной ассоциации. Временная ссылка представляет связь использования между двумя компонентами. Вместо обычной ассоциации она может быть обеспечена параметром процедуры или локальной переменной, которая служит целью операции. Преимущество портов и интерфейсов в том, что эти два компонента не обязаны ничего "знать" друг о друге на этапе дизайна, до тех пор пока совместимы их интерфейсы.
Коннекторы изображаются двумя способами (рис. 3).
Рис.3. Коннекторы
Если два компонента явно связаны друг с другом (либо напрямую, либо через порты), достаточно провести линию между ними или их портами. Если же два компонента подключены друг к другу, потому что имеют совместимые интерфейсы, вы можете использовать нотацию "шарик–гнездо", чтобы показать, что между этими компонентами не существует постоянной связи, хотя они и соединены внутри объемлющего компонента. Вы в любое время подставите вместо каждого из них любой другой компонент, если он удовлетворяет интерфейсу.
Также можно связать внутренние порты с внешними портами объемлющего компонента. В таком случае речь идет о делегирующем коннекторе, поскольку сообщения из внешнего порта делегируются внутреннему. Подобная связь изображается стрелочкой, направленной от внутреннего порта к внешнему. Вы можете представить ее двояко, как вам больше нравится. Во-первых, можно считать, что внутренний порт – то же самое, что и внешний; он вынесен на границу и допускает подключение извне. Во-вторых, примите во внимание, что любое сообщение, пришедшее на внешний порт, немедленно передается на внутренний, и наоборот. Это неважно – поведение будет одинаковым в любом случае.
В примере на рис. 3 показано использование внутренних портов и разных видов коннекторов. Внешние запросы, приходящие на порт OrderEntry (ПередачаЗаказа), делегируются на внутренний порт подкомпонента OrderTaking (ПриемЗаказа). Этот компонент, в свою очередь, отправляет свой вывод на свой порт OrderHandoff (ПереводЗаказа). Последний подключен по схеме "шарик-гнездо" к подкомпоненту OrderHandling (УправлениеЗаказами). Данный вид подключения предполагает, что у компонентов не существует знаний друг о друге; вывод может быть подсоединен к любому другому компоненту, который соответствует интерфейсу OrderHandOff. Компонент OrderHandling взаимодействует с компонентом Inventory (Опись) для поиска элементов на складе. Это взаимодействие выражено прямым коннектором; поскольку никаких интерфейсов не показано, можно предположить, что данное подключение более плотно, то есть обеспечивает более сильную связь. Как только элемент найден на складе, компонент OrderHandling обращается к внешнему сервису Credit (Кредит) – об этом свидетельствует делегирующий коннектор к внешнему порту changing (изменение).
Как только внешний сервис Credit дает ответ, компонент OrderHandling связывается с другим портом ShipItems (ДеталиДоставки) компонента Inventory, чтобы подготовить пересылку заказа. Компонент Inventory обращается к внешнему сервису Fullfillment (Исполнение) для осуществления доставки.
Диаграмма компонентов показывает структуру и возможные способы доставки сообщений компонента. Последовательность сообщений в компоненте она, однако, не отражает. Последовательности и другие виды динамической информации могут быть представлены на диаграмме взаимодействия.
Внутренняя структура, включая порты, части и коннекторы, может быть использована как реализация любых классов – не обязательно компонентов. На самом деле нет большой разницы в семантике между классами и компонентами. Однако зачастую удобно применять соглашение о том, что компоненты используются для инкапсуляции концепций, имеющих внутреннюю структуру (в частности, таких, которые не отображаются непосредственно на отдельный класс в реализации).
На следующем шаге рассмотрим типичные приемы моделирования структурированных классов в UML.