Классы составляют структурный костяк
объектно-ориентированной системы. Хотя они исключительно полезны, но нужно
нечто большее для структурирования больших систем, которые могут состоять из
сотен классов.
Пакет (package)
– это инструмент группирования, который позволяет взять любую конструкцию UML и
объединить ее элементы в единицы высокого уровня. В основном пакеты служат для
объединения классов в группы, и именно этот способ их применения я здесь
описываю, но помните, что пакеты могут применяться для любой другой конструкции
языка UML.
В модели UML каждый класс может
включаться только в один пакет. Пакеты могут также входить в
состав других пакетов, поэтому мы остаемся в иерархической структуре, в которой
пакеты верхнего уровня распадаются на подпакеты со
своими собственными подпакетами, и так далее, до
самого низа иерархии классов. Пакет может содержать и подпакеты, и классы.
В терминах программирования пакеты в UML
соответствуют таким группирующим конструкциям, как пакеты в Java
и пространства имен в C++ и .NET.
Каждый пакет представляет пространство
имен (namespace), а это означает, что каждый класс
внутри собственного пакета должен иметь уникальное имя. Если я хочу создать
пакет с именем Date, а класс Date
уже существует в пакете System, то я обязан поместить
его в отдельный пакет. Чтобы отличить один класс от другого, я могу
использовать полностью определенное имя (fully qualified name), то есть имя,
которое указывает на структуру, владеющую пакетом. В языке UML в именах пакетов
используются двойные двоеточия, поэтому классы дат могут иметь имена System: atenMartinFowler: :Util: ate.
На диаграммах пакеты изображаются в виде
папок с закладками (рис. 7.1). Можно показывать только имя пакета или имя
вместе с его
содержимым. В любом случае можно
использовать либо полностью определенные имена, либо обычные имена. Изображение
содержимого с помощью значков классов позволяет показать все особенности
класса, вплоть до изображения диаграммы классов внутри пакета. Простое
перечисление имен имеет смысл, если вы хотите лишь показать, какие классы
входят в той или иной пакет.
Вполне можно встретить класс, например с именем Date (как в java.util), а не с полностью определенным именем. Этот
стиль является соглашением, в основном принятым в Rational
Rose, которое не входит в стандарт языка.
UML разрешает классам в пакетах быть
открытыми (public) или закрытыми (private).
Открытые классы представляют часть интерфейса пакета и могут быть использованы
классами из других пакетов; закрытые классы недоступны извне. В различных
средах программирования действуют различные правила в отношении видимости их
группирующими конструкциями; необходимо придерживаться правил своего
программного окружения, даже если это идет вразрез с правилами UML.
В таких случаях полезно сократить
интерфейс пакета, экспортируя только небольшое подмножество операций, связанных
с открытыми классами пакета. Можно сделать это, присвоив всем классам
модификатор видимости private (закрытый), так чтобы
они были доступны
только классам данного пакета, а также
создав дополнительные открытые классы для внешнего использования. Эти
дополнительные классы, называемые Facades (Фасады)
[21], делегируют открытые операции своим более застенчивым соратникам по
пакету.
Как распределить классы по пакетам? Это
действительно сложный вопрос, на который может ответить только специалист с
большим опытом работы в области проектирования. В этом деле могут помочь два
правила: общий принцип замыкания (Common Closure Principle) и общий
принцип повторного использования (Common Reuse Principle) [30]. Общий
принцип замыкания гласит, что причины изменения классов пакета должны быть
одинаковые. Общий принцип повторного использования утверждает, что классы
должны использоваться повторно все вместе. Большинство причин, по которым
классы должны объединяться в пакет, проистекают из зависимостей между классами,
к которым я сейчас и перехожу.