Analysis Patterns 1.0 Help

А. Техники и обозначения

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

A.1 Диаграммы типов

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

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

Один из сильных претендентов — унифицированный язык моделирования (UML) компании Rational Software [2]. Но есть две проблемы с использованием этого метода для книги. Во-первых, это вопрос времени. Эта книга была написана в 1994 и 1995 годах, а унифицированный язык моделирования был опубликован только после того, как книга была полностью готова. Даже сейчас, когда я пишу эту книгу, нотация доступна только в предварительной версии, и компания Rational обсуждает значительные изменения, прежде чем выпустить официальный релиз. Вторая проблема заключается в том, что Unified Modeling Language сосредоточен на моделировании реализации, а не на концептуальном моделировании, а эта книга посвящена концептуальным паттернам.

Я выбрал нотацию Оделла [5] для диаграмм типов прежде всего потому, что его подход является наиболее концептуальным из всех основных ОО методов. Однако я адаптировал ее в ряде мест, чтобы она лучше соответствовала моим потребностям.

Большинство методов в той или иной форме используют технику структурного моделирования. Для учебника по этой теме больше всего подходит книга Оделла [5], поскольку он использует очень концептуальный подход. Разработчику также следует прочитать более ориентированную на реализацию книгу, например, книгу Гради Буча [1], чтобы получить представление о перспективах реализации. Кук и Дэниелс [4] дают наиболее строгое описание структурного моделирования и достойны прочтения уже за это.

A.1.1 Тип и класс

Отправной точкой является понятие типа, представленное прямоугольником. Очень важно, что я использую слово тип, а не класс. Очень важно понимать разницу между ними. Тип описывает интерфейс класса. Тип может быть реализован многими классами, а класс может реализовать множество типов. Один тип может быть реализован многими классами с различными языками, компромиссами в производительности и так далее. Один класс также может реализовывать множество типов, особенно если речь идет о подтипах. Различие между типом и классом очень важно для ряда методов проектирования, основанных на делегировании, как это обсуждается в книге «банды четырех» [3]. Эти два термина часто путают, потому что большинство языков не делают явного различия. Действительно, большинство методов анализа и проектирования не делают явного различия.

Я считаю полезным рассматривать построение диаграмм типов (это различие может быть применимо и к другим методам, но наиболее ярко оно проявляется в отношении структурных моделей) с трех точек зрения: концептуальной, спецификационной и реализационной [4]. Концептуальные модели моделируют то, как люди думают о мире. Это полностью мысленные картины, которые игнорируют любые технологические вопросы. Концептуальные модели могут различаться в зависимости от того, представляют ли они реальный мир или то, что мы знаем о нем. В качестве примера можно привести человека и дату его рождения. В реальном мире все люди имеют дату рождения, поэтому разумно моделировать дату рождения как обязательный атрибут человека. Однако мы можем знать человека, не зная его даты рождения. Таким образом, для многих областей дата рождения может быть необязательной в концептуальной модели, отражающей то, что мы знаем о мире. Это различие может быть очень важным для исторической информации. Модель, отражающая структуру мира в том виде, в каком она есть, часто может быть лучше всего нарисована как моментальный снимок момента времени. Однако если она представляет то, что мы знаем, то часто должна отражать и наши воспоминания. Модели, представленные в этой книге, рассматриваются с точки зрения создания модели того, что мы знаем о мире, поскольку именно такая перспектива наиболее полезна в информационных системах.

Модели спецификаций — это модели, которые могут быть использованы для определения интерфейса программных компонентов в системе. Модели спецификаций могут быть неявными или явными. Примером явной модели спецификации может служить заголовочный файл C++, в котором подробно описано, какие операции существуют, их параметры и возвращаемые типы. Неявные модели спецификации должны быть объединены с некоторыми соглашениями, которые показывают, как они разрешаются в явный интерфейс. Например, атрибут birthdate в неявной спецификационной модели разрешается в операции birthdate и birthdate: aDate для Smalltalk, и в операции Date getBirthdate() const и void setBirthDate(Date) для C++.

Неявные модели спецификаций могут быть ближе к концептуальным моделям, чем явные модели, и они также могут нести больше информации, чем многие явные интерфейсы. Интерфейсы C++ и Smalltalk не содержат большого количества информации о правилах использования частей интерфейса. Eiffel, в котором есть утверждения, может быть более полной, но менее понятной, чем неявная модель, которая близко следует концептуальной модели.

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

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

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

A.1.2 Ассоциации, атрибуты и агрегация

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

Мне нравится проводить различие между ассоциацией и сопоставлением. Сопоставление (mapping) — это направленная связь от одного типа к другому. Ассоциация содержит одно или два сопоставления. Однонаправленная ассоциация — это только одно сопоставление, и ее можно рассматривать как то же самое, что и сопоставление. Двунаправленная ассоциация содержит два сопоставления, которые, как говорят, являются инверсиями друг друга. Это не совсем то же самое, что обратные функции в математике. По сути, это означает, что если вы перейдете по сопоставлению и его инверсии, то получите коллекцию объектов, включающую тот, с которого вы начали. Таким образом, если клиент просматривает набор заказов, которые он сделал, каждый из этих заказов указывает обратно на этого клиента. Термин «источник» (или домен) указывает на тип, с которым оно сопоставляется, а термин «цель» (или диапазон) указывает на тип, с которым сопоставляется отображение. (Например, в связке «клиент — заказ» клиент является источником, а заказ — целью). Когда имя появляется вместе с ассоциацией, это имя одного из отображений. Вы можете определить, какое это отображение, по расположению имени в строке ассоциации: Если цель находится впереди, а источник — сзади, то имя находится слева.

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

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

Ключевым аспектом ассоциаций является кардинальность (иногда называемая множественностью). Она определяет такие вещи, как количество компаний, в которых может работать человек, и количество детей у матери. Кардинальность — это характеристика отображения, а не ассоциации: Каждое отображение имеет свою собственную кардинальность. Существует множество символов для обозначения кардинальности; на рисунке A.1 показаны те, которые я использую в этой книге. Отображения, верхняя граница которых равна единице, называются однозначными, а c верхнeй границей больше единицы, называются многозначными. Предполагается, что многозначные отображения представляют собой множество, если не указано иное (короткое семантическое утверждение).

A 1

Рисунок A.1 Символы для обозначения кардинальности, используемые в этой книге.

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

Некоторые методы используют отношения агрегирования, которые представляют собой отношения «часть/целое» (например, молоток состоит из головки и стержня). В этой книге я не очень часто использую агрегацию. Я не считаю эту концепцию очень полезной для доменных моделей, потому что большая часть ее семантики относится к любой ассоциации. Таким образом, она становится еще одним элементом обозначения, который нужно запомнить и о котором нужно спорить, а результат спора обычно не очень важен в любом случае. Однако я использую его в прикладном уровне (см. раздел 13.6).

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

Существует множество других вариаций на тему ассоциаций. Я стараюсь максимально упростить ситуацию. Некоторые полезные вариации обсуждаются в главе 15 как шаблоны ассоциаций.

A.1.3 Обобщение

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

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

Наследование и подклассификация в ОО-языках — это подход к реализации, при котором подкласс наследует данные и операции суперкласса. У него много общего с подтипированием, но есть и важные различия. Подклассификация — это только один из способов реализации подтипизации (см. раздел 14.2). Подклассификация может использоваться и без подтипирования, но большинство авторов справедливо не одобряют такую практику. Новые языки и стандарты все чаще стараются подчеркнуть разницу между наследованием по интерфейсу (подтипирование) и наследованием по реализации (подклассификация).

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

Концептуально говоря, множественная классификация — это более естественный способ мышления. Однако большинство ОО-языков, и, конечно, основные C++ и Smalltalk, используют подход с одной классификацией. Многие методы также используют однократную классификацию. Компромисс заключается в выборе между концептуально более естественным подходом, который требует больше усилий при преобразовании в код, и более привязанным к реализации подходом, который легче преобразовать. Я предпочитаю более концептуальный подход и использую множественную классификацию в этой книге.

При использовании множественной классификации мы должны показать, какие комбинации являются допустимыми, сгруппировав подтипы в разделы, как показано на рисунке A.2. Типы в одном разделе являются непересекающимися; то есть ни один объект не может быть экземпляром более чем одного типа в одном разделе. Таким образом, невозможно, чтобы супертип был одновременно подтипом-1 и подтипом-2. Неполное разбиение указывает на то, что экземпляр супертипа не обязательно должен быть экземпляром подтипа в пределах этого разбиения. Полное разбиение означает, что каждый экземпляр супертипа должен быть также экземпляром подтипа в пределах этого разбиения.

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

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

A 2

Экземпляр супертипа может быть подтипом-1 и подтипом-4, но не подтипом-1 и подтипом-2 одновременно.

Рисунок A.2 Обобщающие обозначения.

Использование динамической классификации выявляет тонкое различие между концептуальной и реализационной моделями. В концептуальной модели все подтипы считаются динамическими, если это явно не отрицается коротким семантическим утверждением [immutable]. Это отражает не только изменения, которые могут происходить в мире, но и наши меняющиеся знания о них. Для некоторых предприятий может быть верно, что личный клиент не может превратиться в корпоративного. Также может оказаться, что клиент, которого мы считали личным, на самом деле является корпоративным. Здесь наши знания о мире подразумевают динамическую классификацию, даже если сам мир статичен. Информационные системы обычно строятся на основе наших знаний о мире, поэтому концептуально подтипизация является динамической.

Однако нельзя игнорировать дополнительную сложность обработки динамической классификации. Поэтому концептуально динамические подтипы часто объявляются статическими в модели спецификации. Это фактически означает, что, хотя мы знаем, что классификация может измениться, это происходит достаточно редко, чтобы мы не хотели прилагать дополнительные усилия (и затраты) для ее поддержки. Если это все же произойдет, пользователям придется разбираться с этим путем копирования и замены. Для многих ситуаций дина-мизм достаточно редок, чтобы такой подход был оправдан. Гибкость в долгосрочной перспективе можно сохранить, обеспечив одинаковый интерфейс аксессора в любом случае (см. раздел 14.2.6).

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

Если вы используете метод, в котором не применяется множественная динамическая классификация, то вам нужно будет преобразовать модели, используя шаблоны, разработанные в разделе 14.2.

A.1.4 Правила и семантические утверждения

Ассоциации и подтипы позволяют нам сказать о типах многое, но не все. У меня может быть объект полиса страхования жизни с отображениями для страхователя и бенефициаров. Я могу использовать ограничения кардинальности, чтобы отразить такие утверждения, как то, что есть только один страхователь, но может быть много бенефициаров; однако эти ограничения не позволяют нам сказать, что страхователь не должен быть бенефициаром. Для этого нам нужно более гибкое ограничение. Ограничение — это логическое выражение о типе, которое всегда должно быть истинным. Ограничения часто отсутствуют в ОО-методах, хотя они уже давно присутствуют в Eiffel (где они называются инвариантами).

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

A 3

Рисунок A.3. Нотация для семантических утверждений.

MAРКЕР

ПРИКРЕПЛЕНО К

ЗНАЧЕНИЕ

[abstract]

Тип

Тип не может иметь экземпляров, которые не являются экземплярами какого-либо подтипа.

[abstract]

Отображение

Должно быть переопределено подтипами домена. Источник также является абстрактным.

[class]

Отображение

Отображение идет от класса, а не от экземпляров. Это эквивалент переменных класса или статических членов.

[Dag]

Рекурсивная ассоциация

Объекты, связанные с этой ассоциацией, формируют направленный ациклический граф.

[Dag]

Отображение

Отображение возвращает направленный ациклический граф объектов.

[global]

Пакет

Пакет виден всем другим пакетам.

[hierarchy]

Рекурсивная ассоциация

Объекты, связанные с этой ассоциацией, формируют иерархию.

[hierarchy]

Множественное отображение

Отображение возвращает иерархию объектов.

[historic]

Историческое отображение

Сохраняет историю предыдущих подключений (см. Раздел 15.3).

[immutable] или [imm]

Отображение

Отображение не может быть изменено после создания экземпляра.

[immutable] или [imm]

Разделение

Подтипы статичны. Объекты не могут менять тип в пределах этого разделения.

[key: a type]

Отображение

Отображение с ключом (см. Раздел 15.2).

[list]

Множественное отображение

Отображение возвращает упорядоченную коллекцию (список) объектов.

[multiple hierarchies]

Рекурсивная ассоциация

Объекты, связанные с этой ассоциацией, образуют несколько иерархий.

[singleton]

Тип

Тип может иметь только один экземпляр.

[number1, number2]

Отображение

number1 — нижняя граница, а number2 — верхняя граница отображения.

Не все можно выразить в коротком смысловом высказывании. Когда требуется больше места, я использую длинное смысловое высказывание, которое содержит больше текста в ящике с собачьими ушками. Длинное семантическое высказывание имеет заголовок, указывающий на то, что оно описывает. Эти заголовки перечислены в таблице А.2.

Заголовок

ПРИКРЕПЛЕНО К

ЗНАЧЕНИЕ

Ограничение

Тип

Утверждение, которое должно быть истинным для всех экземпляров этого типа.

Производное

Производное отображение

Способ получения отображения. Реализации могут выбрать другой эквивалентный способ.

Экземпляр

Тип

Список всех допустимых экземпляров этого типа.

Метод

Операция

Указывает метод для операции.

Заметка

Любое

Неформальный комментарий.

Перегрузка

Тип

Указывает, как тип перегружает некоторую функцию суперкласса.

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

A.1.5 Основные типы

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

В объектных системах мы можем легко определить новые типы, которые обладают многими из тех же возможностей, что и встроенные типы. Классический пример из Smalltalk — дробь. В Smalltalk дробь работает так же, как и любое другое число. Если выполнить вычисление 1/3 в Smalltalk, ответом будет дробь 1/3, а не какая-то псевдобесконечно повторяющаяся десятичная дробь.

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

В таблице A.3 перечислены основные типы, используемые в этой книге.

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

TYPE

ОПИСАНИЕ

Boolean

Истинно или ложно, с обычными операциями.

Currency

Подтипы единицы, представляющие валюты (например, доллары США, фунты стерлингов, йены).

Date

Обычные даты (например, 1-Апр-1995).

Duration

Подтип количества, единицы которого — время (например, 5 дней, 3 часа). Обратите внимание, что нельзя переводить дни в месяцы.

Integer

Обычные целые числа (…-1, 0, 1, 2…).

Magnitude

Тип, поддерживающий операции сравнения, такие как <, >, =, ≥, ≤.

Money

Подтип количества, единицы которого — валюта (например, $5, 250 FFR).

Number

Суперкласс для целых, действительных и дробных чисел.

Quantity

Тип с числом и единицами измерения (например, 4 дюйма) (см. Раздел 3.1).

Range

Диапазон между двумя величинами (см. Раздел 4.3).

Real

Обычные действительные числа.

String

Короткий текст. Нет фиксированного предела, но обычно это короткий текст в одну строку. Более длинные тексты используют тип text.

Text

Длинный текст, обычно с форматированием.

Time

Время суток (например, 1:20 p.m.). Не привязано к определенной дате (см. Timepoint).

Timepoint

Момент времени. Может быть только датой или комбинацией даты и времени.

Time Period

Период с начальной и конечной точками времени. Период может показывать пересечение с другим периодом или нахождение в нем момента времени. Это диапазон точек времени.

Time Reference

Суперкласс для периода времени и момента времени.

Unit

Единица для количества (например, дюймы, ньютоны).

Таблица А.3 Фундаментальные типы использованные в этой книге

Некоторые авторы называют такие типы литералами; однако другие авторы используют термин литерал для обозначения необъектных типов (например, типа real в C++), поэтому я использую термин фундаментальный тип.

В этой книге я не пытался дать полную спецификацию фундаментальных типов. Считайте это упражнением для читателя (или для будущего издания). Спецификация некоторых из этих типов дана в работе Кука и Дэниелса [4].

A.2 Диаграммы взаимодействия

Диаграммы взаимодействия показывают, как несколько объектов взаимодействуют друг с другом, чтобы что-то сделать. Диаграмма взаимодействия состоит из нескольких вертикальных линий, которые представляют объекты. Стрелки между линиями представляют собой сообщения, передаваемые между объектами, последовательность которых обозначается движением вниз по листу бумаги, как показано на рисунке A.4. Диаграммы взаимодействия широко используются и просты для понимания. Необычно то, что я использую стрелки с двойной головкой, чтобы показать, где одно и то же сообщение посылается многим объектам, как это происходит в цикле или при итерации по коллекции. Я также иногда использую пунктирную линию, чтобы показать возвращаемое значение; это не то, что я делаю постоянно, но иногда это полезно, когда ситуация становится сложной.

A 4

Рисунок A.4 Нотация, используемая для диаграмм взаимодействия.

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

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

Поскольку диаграммы взаимодействия настолько просты, вам не понадобится большой учебник по ним, если вы не использовали их раньше. Хорошим источником для получения более подробной информации является книга Гради Буча [1].

A.3 Диаграммы событий

Диаграммы событий — еще одна форма поведенческой модели, которую я использую. Хотя они сложнее, чем диаграммы взаимодействия, они позволяют задать полный контроль. Они также способны выражать параллельное поведение, что очень полезно в бизнес-моделировании.

Флажки на диаграмме событий представляют операции, которые завершаются по сигналу события. Триггерное правило указывает, что событие запускает операцию. Параллелизм проявляется, когда для типа события определено более одного правила запуска. Таким образом, на рисунке A.5 тип события, сигнализирующий о завершении операции-1, параллельно запускает операции-2 и операции-3. Это означает, что операция-2 и операция-3 могут выполняться в любом порядке или одновременно. Параллелизм также может происходить при множественном срабатывании, что показано стрелкой с двойной головкой. Это означает, что событие запускает операцию многократно, например при итерации по коллекции. Метка на линии указывает, над какой коллекцией выполняется итерация.

A 5

Рисунок A.5. Нотация для диаграмм событий.

Если триггерное правило ведет к операции через управляющее условие, операция вызывается только в том случае, если управляющее условие (булево выражение) равно true. Управляющее условие часто используется для синхронизации параллельных потоков. Каждый поток запускает условие, которое должно быть истинным только в соответствующей точке синхронизации.

Два распространенных условия управления — это условие and и условие z. Условие and истинно только тогда, когда все входящие правила запуска сработали один раз. Оно показано знаком & в ромбе. Условие z истинно, когда на диаграмме нет операций, которые должны быть запущены, то есть когда все тихо и диаграмма перешла в спящий режим. Оно обозначается символом z в ромбе (как в zzzzzz). Условие z часто используется в конце диаграммы, чтобы синхронизировать ее завершение.

Другая условная логика — это логика разбиения, как в операции-3. Событие подтипизируется в зависимости от результата операции. На событие супертипа может быть наложено правило триггера, чтобы указать триггер, который срабатывает независимо от результата. Разделение работает так же, как и в структурных моделях. Событие может иметь множество разделов, раздел может содержать любое количество событий, а разделы могут быть определены друг над другом на любую желаемую глубину. Любое событие будет являться экземпляром только одного типа события из каждого раздела.

Диаграммы событий концептуальны в том смысле, что они говорят только о том, как работает некоторый процесс, а не о том, какие объекты его выполняют. Таким образом, они хорошо дополняют диаграммы взаимодействия. Учебное пособие по ним см. в статье Оделла [5].

A.4 Диаграммы состояний

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

Каждое состояние изображается в виде коробки, как показано на рисунке A.6. Коробки связаны между собой переходами, которые показывают, как объект может перейти из одного состояния в другое. Переход помечен событием, которое вызывает переход. Если у перехода есть защита, то переход происходит только тогда, когда происходит событие, и защита оценивается как true. Защита — это булево выражение. Если у перехода есть действие, то это действие выполняется во время перехода в новое состояние. Состояния могут быть обобщены на суперсостояния. Суперсостояние можно использовать для определения переходов, которые затем применяются ко всем подсостояниям.

A 6

Рисунок A.6 Нотация для диаграмм состояний в этой книге.

Простой учебник по диаграммам состояний Харела можно найти в книге Буча [1]. Для более подробного изложения материала лучше всего подойдет книга Кука и Дэниелса [4]. В этой книге я не так часто использую диаграммы состояний, и уж точно не с такой силой, как диаграммы состояний Харела, но время от времени они всплывают.

A.5 Диаграммы пакетов

В больших моделях нам нужен способ организовать массу типов, которые появляются на диаграмме типов. Одна большая диаграмма типов одновременно слишком сложна для понимания человеком и слишком трудна для управления программным обеспечением. Для человека большую диаграмму можно разбить на страницы, но произвольный выбор страниц мало что дает для управления программным обеспечением. Пакетные диаграммы, как показано на рисунке A.7, обеспечивают более контролируемый механизм.

11 3

Эта диаграмма взята с рисунка 11.3. Я использую нотацию Unified Modeling Language компании Rational Software [2] для пакетов, поскольку считаю ее более понятной, чем оригинальная нотация Буха.

Рисунок A.7 Нотация для описания пакетов.

Пакет (также называемый категориальным доменом, кластером или подсистемой) — это группа типов (или классов). Тип может принадлежать только одному пакету. Обычно типы назначаются пакетам таким образом, чтобы типы, которые часто взаимодействуют друг с другом, помещались в один и тот же пакет. Любой тип в пакете может получить доступ к любой функции любого другого типа в том же пакете.

Пакеты связаны отношениями видимости. Если тип клиента хочет использовать тип сервера в другом пакете, между типом клиента и типом сервера должно существовать отношение видимости. Это необходимо для любого сервиса: вызова операции, хранения атрибута или передачи в качестве параметра.

Видимость отличается от предварительного условия. Необходимое условие подразумевает, что для функционирования одного пакета необходимо присутствие другого. Необходимые условия являются транзитивными: Если пакет C является необходимым условием для пакета B, а пакет B является предварительным условием для пакета A, то пакет C является необходимым условием для пакета A. Эта транзитивность не верна для видимости. Пакет A может не иметь видимости для пакета C; более того, пакет B может быть специально разработан, чтобы скрыть пакет C от пакета A — в этом суть многоуровневой архитектуры. Предпосылки и видимости часто путают, потому что языки программирования часто объединяют эти два понятия. Заголовочные файлы C++ и пререквизиты Envy определяют пререквизиты и обеспечивают видимость всех пререквизитов, что исключает использование одного пакета для скрытия другого. Все видимости должны быть явно объявлены в пакете. Таким образом, на рисунке A.7 пакет приложения управления рисками должен иметь явную видимость пакета портфеля, чтобы иметь возможность использовать его сервисы. Если бы такой видимости не было, пакет портфеля все равно был бы необходимым условием (через пакет оценки), но видимости не было бы. Видимость предполагает наличие предпосылок, но не наоборот.

Внутри пакета типы могут быть публичными или приватными. Публичные типы видны пакетам, у которых есть видимость; приватные типы могут использоваться только типами в том же пакете. Пакеты можно сделать глобальными, в этом случае они будут видны всем остальным пакетам. Это необходимо для таких общих компонентов, как целые числа, строки и коллекции.

При разработке большой системы мы стараемся минимизировать видимость между пакетами, чтобы система имела меньше зависимостей и, следовательно, легче управлялась. В этой книге я рассматриваю пакеты в основном в главах 11 и 12.

Хотя такая модель необходима для больших систем, она мало обсуждается в методах. Буч [1] представил основные идеи, которые я использую здесь, но его описание очень краткое, в основном потому, что трудно обсуждать эту тему без существенного примера. Этот недостаток был исправлен Робертом Мартином, который приводит ряд примеров использования пакетных моделей [6].

Ссылки

  1. Booch, G. Object-Oriented Analysis and Design with Applications (Second Edition). Redwood City, CA: Benjamin/Cummings, 1993.

  2. Booch, G. and J. Rumbaugh. Unified Method for Object-Oriented Development Rational Software Corporation, Version 0.8, 1995.

  3. Gamma, E., R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.

  4. Cook, S. and J. Daniels. Designing Object Systems: Object-Oriented Modelling with Syntropy. Hemel Hempstead, UK: Prentice-Hall International, 1994.

  5. Martin, J. and J. Odell. Object-Oriented Methods: A Foundation. Englewood Cliffs, NJ: Prentice-Hall, 1995.

  6. Martin, R.C. Designing Object-Oriented C++ Applications Using the Booch Method. Englewood Cliffs, NJ: Prentice-Hall, 1995.

Last modified: 16 January 2025