11. Торговые пакеты
Чтобы полностью понять эту главу, вам необходимо сначала прочитать главы 9 и 12. Разработка больших информационных систем сопряжена с особыми трудностями. Основной способ работы с крупномасштабной системой заключается в ее декомпозиции на более мелкие системы. Это требует определенной формы архитектурного моделирования, о чем говорится в разделе A.5.
Первым организационным инструментом любой информационной системы является слоёная архитектура, рассмотренная в главе 12. Эта архитектура определяет многие пакетные подразделения системы. Однако в больших системах модель домена становится слишком большой для одного пакета. В этой главе мы рассмотрим, как можно разделить большую доменную модель. Концепции пакета и видимости (см. раздел A.5) снова используются в качестве основного инструмента для разделения. Примерами служат концепции торговли из главы 9. Первый пример рассматривает, как организовать модели сценариев и портфелей. Основная проблема заключается в наличии нескольких уровней доступа к пакету (11.1). Приложение для управления рисками использует сценарии для получения информации, необходимой для оценки портфелей. Другому приложению требуется настройка и управление сценариями. Обоим приложениям нужен доступ к типам сценариев, но им нужны разные уровни доступа. Разные клиенты, нуждающиеся в разных интерфейсах — распространенная проблема. Решения включают разрешение пакету иметь несколько протоколов и использование разных пакетов.
Отношения между контрактами и участниками поднимают проблему взаимной видимости (11.2). Предлагаются три решения: односторонняя видимость между контрактом и участниками, помещение обоих в один пакет или в отдельные взаимно видимые пакеты. Все три решения имеют существенные недостатки.
Последний паттерн исследует пакеты подтипов (11.3), рассматривая, как расположить производные, рассмотренные в главе 10, в структуре пакета. Этот паттерн иллюстрирует, что подтипы могут быть помещены в пакет отдельно от своих супертипов с видимостью от подтипа к супертипу.
11.1 Несколько уровней доступа к пакету
Портфели формируются из контрактов с использованием рыночных индикаторов в качестве описаний. Сценарии используются независимо для разработки цен на рыночные индикаторы. Портфели и контракты должны использовать сценарии для определения своей стоимости, но сценарии не нуждаются в знании портфелей и контрактов, как показано на рис. 11.1.

Рисунок 11.1 Первоначальная картина видимости пакетов.
Для проведения оценок портфелю требуется только цена рыночного индикатора. Пакету портфеля не нужно знать, как настроен сценарий. Таким образом, хотя тип элемента сценария должен быть виден портфелю, чтобы можно было отправить сообщение getQuote
, нет необходимости видеть подтипы с определением того, как формируется котировка. На самом деле мы можем пойти дальше и сказать, что даже элемент сценария не является полезным для портфеля. Лучшим подходом было бы представить интерфейс портфеля, как показано на рис. 11.2. Этот интерфейс имеет сопоставление по ключу (см. раздел 15.2) на сценарий, который принимает в качестве аргумента рыночный индикатор. Поскольку никакие другие свойства элемента сценария не важны, интерфейс для пакета портфеля прост.

Это лучший интерфейс для пакета портфеля, которому не нужно знать об элементах сценария.
Рисунок 11.2. Интерфейс пакета сценария, скрывающий элементы сценария.
Такой подход требует двух разных типов пакетов сценария: один для интерфейса с портфелем, другой — для настройки сценариев. Таким образом, необходимо нечто большее, чем простое назначение типов пакетам. Ближайший и очевидный подход — разделить типы в пакете сценария на публичные и приватные. Публичные типы — это те классы из пакета сценария (например, портфель), которые видны другим пакетам. Приватные типы видны только типам внутри пакета сценария. В данном случае сценарий является публичным типом, а элемент сценария — приватным типом. Эту логику можно распространить и на операции. Публичные операции могут быть публичными внутри пакета и публичными для других пакетов. Хотя это и представляет собой тонкую степень контроля, ее может быть слишком сложно поддерживать. Искусство хорошего дизайна видимости заключается в том, чтобы выбрать такую степень видимости, которая будет достаточно тонкой, чтобы быть полезной, но не настолько тонкой, чтобы превратить портфель в кошмар поддержки. (Вещи, которыми сложно управлять, как правило, не управляются, что приводит к появлению устаревших и бесполезных моделей).
Одна из проблем такого подхода заключается в том, что пользователям требуется программное обеспечение для создания сценариев и работы с ними. Для этого требуются компоненты на слоях доменной логики и презентации, как обсуждалось в главе 12. Таким образом, модель должна включать пакет приложений для управления сценариями, отдельный от пакета сценариев. На рис. 11.3 показано добавление пакета приложений для управления сценариями и пакета приложений для управления рисками. Однако такой подход не будет работать с описанным выше подходом «публичный/приватный», поскольку для приложения управления сценариями требуются приватные типы пакета сценариев. Хотя и управление портфелем, и управление сценариями требуют видимости пакета сценариев, им нужны разные типы видимости.

Проблема заключается в том, что приложению для управления сценариями требуется гораздо больший интерфейс к пакету сценариев, чем пакету портфеля.
Рисунок 11.3. Добавление пакетов приложений на Рисунок 11.1.
Одно из решений этой проблемы, предложенное Вирфсом-Броком (Wirfs-Brock) [1], позволяет пакету иметь более одного протокола.(Вирфс-Брок использует термин контракт, который в данном примере смущает, поэтому я использую протокол). В нашем оригинальном шаблоне мы задали протокол как набор операций; однако вполне разумно сделать его просто набором типов, чтобы упростить контроль видимости. Использование отдельных протоколов приводит к диаграмме, подобной рис. 11.4, на которой сценарий имеет два протокола. Тот, который используется портфелем, допускает только поверхностный протокол, в то время как приложение для управления сценарием использует более глубокий протокол. Протоколы показаны полукруглыми портами на контуре пакета. (Я показываю порты только на пакетах с более чем одним протоколом).

Каждый протокол подразумевает отдельный интерфейс.
Рисунок 11.4 Пакеты из рисунка 11.3 с протоколами.
Использование отдельных протоколов — один из способов решения проблемы множественной видимости. Другой способ — ввести дополнительный пакет, как показано на рис. 11.5. Элемент сценария и его подтипы перемещаются из пакета сценария в пакет структуры сценария. Пакет сценария содержит только тип сценария и его простые ассоциации. Пакет портфеля имеет видимость только пакета сценария, в то время как приложение управления сценариями видит и сценарий, и пакет структуры сценария. Мы можем определять новые сценарии с помощью дополнительной видимости.

Рисунок 11.5. Использование дополнительного пакета для структуры сценария.
Внимательному читателю может прийти в голову вопрос, нужно ли сценарию иметь видимость структуры сценария. Ответ на запрос о цене требует использования внутренней структуры. Интригующий аспект наследования и полиморфизма проявляется в этих видимостях. Пакет сценария может содержать класс сценария, который определяет интерфейс, необходимый всем пакетам, работающими со сценарием. Однако этот класс сценария не обязательно должен реализовывать весь интерфейс (и поэтому является абстрактным). Мы можем поместить второй класс сценария в пакет структуры сценария, который реализует интерфейс. Этот второй класс сценария имеет полную видимость содержимого структуры сценария. Любой объект сценария, используемый другим пакетом, является экземпляром класса сценария структуры сценария, но клиенты класса этого не осознают. Все, что они видят — это объект, соответствующий интерфейсу класса сценария из пакета сценариев. Возможно, стоит ввести обозначение, показывающее, где происходит подобное подклассирование через границы пакетов, хотя я его не использую.
Поэтому, когда объект в пакете портфеля отправляет сообщение сценарию, на самом деле он отправляет сообщение экземпляру конкретного класса сценария, который находится в пакете управления сценариями. Однако вызывающая сторона думает, что вызывает экземпляр абстрактного класса сценария, который находится в пакете Сценарии. Объект может отправить сообщение объекту в пакете, который он не видит, при условии, что вызываемый объект является подклассом класса в пакете, который видит вызывающий объект.
Следствием этого является то, что видимость не отражает зависимостей времени компиляции или загрузки. Хотя структура сценария не видна сценарию, сценарий нуждается в структуре сценария для функционирования (строго говоря, он зависит от некоторого пакета, реализующего интерфейс). Структура сценария содержит конкретные подклассы сценария, без которых пакет сценария не может работать.
Хотя в этой схеме требуется два разных класса сценариев, они могут соответствовать одному типу сценария. В этом случае предоставляется новый подтип, позволяющий получить доступ к внутренней структуре сценария для таких приложений, как управление сценариями. Однако можно иметь единый тип, когда другим типам не нужно вызывать специальные функции, присутствующие только в подтипе.
11.2 Взаимная видимость
Добавление пакетов для контрактов и участников вызывает более сложные вопросы. В случае со сценариями и портфелями отдельные пакеты использовались по двум причинам. Во-первых, сценарии и портфели кажутся отдельными кусками модели. Они сами по себе являются сложными разделами, которые как бы составляют единицу труда. Во-вторых, для построения модели сценариев нам не нужны знания о портфелях. Вторая причина является самой сильной, поскольку она приводит к наглядным связям, показанным на рисунке 11.1.
Разумно заключить, что контракты можно составлять и моделировать без знания портфелей. Контракты можно регистрировать независимо от динамической структуры портфеля, используемого для их группировки в целях оценки риска, как показано на рис. 11.6.

Приложению для управления рисками нужны оба пакета, а приложению для ценообразования достаточно знать только о контрактах.
Рисунок 11.6 Пакеты для портфеля и контракта.
Отношения между участниками (группами) и контрактами представляют собой более серьезную проблему. Мы можем привести аргументы в пользу того, чтобы помещать стороны в отдельный пакет. Ряд приложений может искать информацию об участниках, не желая ничего знать о заключаемых с ними сделках. Общий пакет партии может содержать общую информацию об участниках, используемую многими торговыми системами, подобно базе данных контактов. Таким образом, мы можем сделать вывод, что пакет участников является ценным.
Каковы будут отношения между участниками и пакетами контрактов? Участнику было бы полезно знать, какими контрактами он занимается, а контракту — кто является участником контракта. Это подразумевает взаимную видимость между участником и контрактом, как показано на рис. 11.7. Но взаимная видимость может вызвать проблемы в пакетной модели. В целом мы стараемся разрабатывать пакетные модели с учетом слоёной архитектуры и простыми линиями видимости. Многие считают, что в такой архитектуре никогда не должно быть циклов в отношениях видимости, потому что цикл нарушает правило четких слоев. Взаимная видимость — это простейший случай цикла.

Некоторые приложения нуждаются только в одном из пакетов — пакете участников или пакете договора, но оба эти пакета нуждаются друг в друге. Это может подразумевать взаимную видимость. Если взаимная видимость неприемлема, мы можем выбрать одно направление или объединить пакеты.
Рисунок 11.7 Отдельные пакеты участников и контракта.
Чтобы устранить взаимную видимость, мы должны либо изменить характеристики партии или контракта так, чтобы только одна из них знала о другой, либо объединить их в один пакет. Каждая альтернатива имеет свои компромиссы.
Преимущество ограничения видимости между типами Участник и Контракт в одном направлении заключается в том, что это уменьшает связь между этими двумя типами (и их соответствующими пакетами). Если мы удалим отображения от участника к его контрактам (сделав ассоциацию односторонней), мы сможем работать над пакетом участников без необходимости знать что-либо о контрактах. Это уменьшает связанность (участник больше не связан с контрактом), что является преимуществом. Однако пользователь, который хочет узнать, по каким контрактам конкретный участник является контрагентом, должен просмотреть каждый контракт и использовать сопоставление к участнику для формирования набора. Таким образом, мы уменьшили сложность для разработчика пакета участника, но увеличили сложность для разработчика любого приложения, которому необходимо использовать оба типа. Абсолютно правильного ответа здесь нет; мы должны рассмотреть компромиссы в каждом направлении и решить, какой выбор будет менее обременительным.
Если мы примем решение в пользу двусторонней ассоциации, то единственным способом устранить взаимную видимость будет объединение пакетов партии и контракта. Однако это не лишено недостатков. На рис. 11.7 видно, что пакету управления контактами нужно знать только об участниках, но не о контрактах. Объединение этих двух пакетов приведет к удалению этой информации. Управление контактами будет вынуждено иметь большую видимость, чем ему необходимо.
Такая ситуация заставляет меня не запрещать взаимные видимости или другие циклы. Безусловно, циклы должны быть сведены к минимуму. Однако полное их устранение приводит либо к вынужденному компромиссу между односторонними и двусторонними ассоциациями, либо к большим пакетам, клиентам которых не нужна вся видимость, которая подразумевается.
На рис. 11.8 показан еще один пример такой ситуации. Продукт (см. раздел 10.3) добавлен в свой собственный пакет. Предыдущие аргументы приводят к взаимной видимости между продуктом, участником и контрактом. Это приводит к достаточно тесному взаимодействию пакетов доменных моделей. Однако прикладные пакеты должны видеть только часть картины, и у каждого прикладного пакета свои потребности. Три взаимно видимых пакета позволяют нам четко определить эти потребности.

И снова различные потребности приложений могут быть удовлетворены взаимно видимыми пакетами.
Рисунок 11.8 Добавление продукта в пакет.
Другой способ сделать это — поместить протоколы в пакеты. Тогда пакеты участника, продукта и контракта объединяются, а старым пакетам соответствуют три отдельных протокола. Приложения выбирают протоколы так же, как они выбирают пакеты, показанные на рис. 11.8.
Подводя итог, можно сказать, что когда типы естественным образом тесно связаны друг с другом, у нас есть три варианта. Мы можем разделить типы, сделав ассоциации односторонними (но это усложняет задачу для пользователя типов). Мы можем поместить их в один большой пакет (но это означает, что любой пользователь пакета будет иметь доступ ко всему пакету, даже если нужна только его часть). Мы можем иметь два взаимно видимых пакета (но это вносит циклы в структуру пакета). Если у вас есть протоколы в пакетах, вы можете иметь один большой пакет с отдельными протоколами.
11.3 Подтипизация пакетов
Видимость проще всего рассматривать при подтипировании. Подтип всегда должен видеть супертип, но мы должны избегать обратного. Поэтому мы добавляем комбинации, опции и барьеры (описанные в главе 10), как показано на рис. 11.9.

Подтипы должны видеть свои супертипы, но не наоборот.
Рисунок 11.9 Добавление различных типов опционов.
Мы также должны избегать взаимной видимости между подтипом и его супертипом. Суть подтипизации заключается в том, чтобы позволить типу быть расширенным без того, чтобы об этом знал супертип. Если мы проектируем типы так, чтобы супертипы знали о своих подтипах, то будущая специализация, скорее всего, будет более сложной, поскольку мы встроили предположения о подтипизации в супертип. Любые усилия по устранению таких зависимостей окупаются последующими усовершенствованиями. Правильное проектирование супертипов обычно требует опыта проектирования нескольких подтипов, поэтому лучше не исправлять супертип до тех пор, пока не будет собрано несколько подтипов.
11.4 Заключительные размышления
Наглядность всегда предполагает компромиссы. Ограничение видимости снижает удобство навигации по модели. При большом количестве односторонних видимостей перемещение по модели может напоминать перемещение по городу с большим количеством улиц с односторонним движением. Двусторонние возможности значительно упрощают навигацию, а значит, требуется меньше кода для написания и поддержки. Однако за такие возможности приходится платить. Чем больше частей системы видят друг друга, тем сложнее контролировать последствия изменений в модели. Ограничение видимости уменьшает эту взаимозависимость.
Разные разработчики ОО-моделей по-разному идут на этот компромисс. Некоторые сильно ограничивают видимость, используя такие приемы, как односторонние ассоциации и графы видимости типов. Я считаю это слишком ограничительным. Я рассматриваю видимость на уровне пакетов, а не типов. Архитектура, представленная в главе 12, разделяет систему на основные слои. Внутри слоя домена можно использовать дополнительные ограничения видимости, но это редко бывает просто. Однако я предпочитаю этот подход из-за своего опыта работы с информационными системами. Другие виды разработки требуют иных компромиссов.
В большинстве проектов архитектура пакетов не рассматривается в деталях. Зачастую в них присутствуют только базовые слои архитектуры, если вообще присутствуют. Это приводит к недостаткам для проекта и затрудняет оценку ценности правильно реализованной архитектурной модели. Только большая практика позволит нам лучше понять обсуждаемые здесь компромиссы.
Если разработка пакетной архитектуры сложна для одного проекта, то при попытке интегрировать информационные системы для крупной организации сложность возрастает в десятки раз. Крупные организации страдают от того, что множество систем не могут взаимодействовать между собой. Даже если аппаратное и программное обеспечение будет доведено до ума, такая интеграция окажется невозможной из-за различий в концепциях, лежащих в основе систем. Одним из общепризнанных решений является моделирование в масштабах всей организации. Однако проблема этого подхода заключается в том, что оно занимает слишком много времени. К тому времени, когда он будет завершен, если вообще будет завершен, усилия обычно дискредитируются и устаревают. Я считаю, что существует верхний предел размера области моделирования, с которым можно справиться за один раз, и он связан с созданием полезных систем, которые оправдывают затраты на моделирование в течение разумного периода времени. Для их интеграции необходимо использовать более оппортунистический подход. Я считаю, что для этой задачи пакеты и видимости являются необходимыми инструментами. Их недостаточно для решения этой задачи, и я не берусь утверждать, что знаю, что еще необходимо. Такая интеграция в масштабах предприятия еще мало изучена, и, как и многие, я только узнал, чего делать не следует!
Ссылки
Wirfs-Brock, R., B. Wilkerson, and L. Wiener. Designing Object-Oriented Software. Englewood Cliffs, NJ: Prentice-Hall, 1990.