Добрый день. На странице https://intuit.ru/studies/professional_skill_improvements/1364/courses/229/lecture/5954
не работает ссылка http://www.omg.org/technology/documents/modeling_spec_catalog.htm#UML |
Диаграмма классов: крупным планом
Как класс изображается на диаграмме UML?
Архитектор программного обеспечения в первую очередь обращает внимание на объекты предметной области. Программист же концентрируется на поведении этих объектов, пользуясь классами, к которым они принадлежат. Вот поэтому-то диаграмма классов и является одной из важнейших диаграмм UML. Она используется для документирования программных систем, и основным ее компонентом является класс. Что такое класс, мы уже говорили ранее, когда знакомились с видами диаграмм UML. В предыдущей лекции мы рассматривали назначение диаграммы классов, знакомились с примерами готовых диаграмм, но не вникали в тонкости обозначений, используемых на диаграмме. В тех примерах все казалось нам очень понятным и логичным. Тем не менее, некоторые нюансы все же следует рассмотреть, и как раз этим мы сейчас и займемся.
Класс на диаграмме изображается в виде прямоугольника, разделенного горизонтальными линиями на три части. В первой части указывается название класса. Как правило, имя класса состоит из одного, максимум двух слов. Вторая часть содержит перечень атрибутов класса, которые характеризуют тот или иной объект этого класса в модели предметной области. Третья часть содержит перечень операций, отражающих его поведение в модели предметной области (рис. 3.1). Все очень просто, не так ли?
А что внутри?
Мы узнали, как класс изображается и выглядит "снаружи". А что же внутри объектов класса? Пользователю об этом знать необязательно, более того, абсолютно не нужно. Для человека, использующего его, объект выступает в роли черного ящика. Скрывая от пользователя внутреннее устройство объекта, мы обеспечиваем его надежную работу. Сейчас мы рассмотрим, как убрать из поля зрения пользователя то, что ему знать не нужно.
Читателя может слегка смутить слово "пользователь", которым мы злоупотребляли в предыдущем абзаце. Зачем вообще пользователю какие-то объекты и классы? Внесем ясность. Программист, использующий в своей программе созданные кем-то компоненты, как раз и выступает в роли такого пользователя. Зачем ему знать что внутри - он знает, какие атрибуты надо модифицировать и какие операции использовать, чтобы заставить объект работать именно так, как ему нужно! Более того, а многие ли из нас знают, как именно устроен и по каким принципам работает, например, телевизор - объект класса "Бытовой прибор"?
Сокрытие от пользователя внутреннего устройства объектов называется инкапсуляцией. Если говорить более "научным" языком, то инкапсуляция - это защита отдельных элементов объекта, не затрагивающих существенных характеристик его как целого. Инкапсуляция нужна не только для того, чтобы создать иллюзию простоты объекта для пользователя (по словам Г. Буча). Но вернемся к примеру с телевизором. Нам этот прибор кажется очень простым только потому, что при работе с ним мы используем простой и понятный интерфейс - пульт дистанционного управления. Мы знаем: для того чтобы увеличить громкость звука, надо нажать вот эту кнопку, а чтобы переключить канал - вот эту. Как телевизор устроен внутри, мы не знаем. Более того - в отсутствие пульта ДУ такое знание было бы неудобным для нас и весьма опасным для самого телевизора, вздумай мы увеличить громкость с помощью паяльника. Поэтому-то пульт ДУ и защищает от нас "внутренности" телевизора! Вот так инкапсуляция реализуется в реальном мире.
В программировании инкапсуляция обеспечивается немного по-другому - с помощью т. н. модификаторов видимости. С их помощью можно ограничить доступ к атрибутам и операциям объекта со стороны других объектов. Звучит это немного пугающе, но на самом деле все просто. Если атрибут или операция описаны с модификатором private, то доступ к ним можно получить только из операции, определенной в том же классе. Если же атрибут или операция описаны с модификатором видимости public, то к ним можно получить доступ из любой части программы. Модификатор protected разрешает доступ только из операций этого же класса и классов, создаваемых на его основе. В языках программирования могут встречаться модификаторы видимости, ограничивающие доступ на более высоком уровне, например, к классам или их группам, однако смысл инкапсуляции от этого не изменяется. В UML атрибуты и операции с модификаторами доступа обозначаются специальными символами слева от их имен:
Символ | Значение |
---|---|
+ | public - открытый доступ |
- | private - только из операций того же класса |
# | protected - только из операций этого же класса и классов, создаваемых на его основе |
Рассмотренный ранее пример с телевизором средствами UML (конечно же, это очень высокоуровневая абстракция) можно изобразить так (рис. 3.2):
Не правда ли, все понятно и предельно просто? Зачем, например, пользователю знать числовые значения частот каналов? Он знает, что достаточно запустить процедуру автоматического поиска каналов и телевизор все сделает за него. Вот вам и инкапсуляция - оказывается, она повсюду вокруг нас. Оглянитесь и подумайте, сколько вещей вокруг имеют скрытые свойства и выполняют скрытые операции. Испугались? Вот то-то же!
Как использовать объекты класса?
Итак, мы рассмотрели инкапсуляцию - одно из средств защиты объектов. Все вроде бы понятно, но как же именно работать с объектом?
Если уж говорить о защите объекта, то чтобы она действительно была эффективной, надо позаботиться о некоем стандартном и безопасном, не зависящим от языка программирования способе доступа к объекту. К тому же такой стандартный способ доступа должен быть простым и с точки зрения использования, и с точки зрения реализации. Вспомните пример с телевизором. Нажимая кнопки на пульте, мы ожидаем, что телевизор откликнется на это действие каким-то определенным образом - именно так, как мы ожидаем, а не иначе. То есть, с одной стороны, пульт ДУ является средством доступа к скрытым операциям, выполняемым телевизором, а с другой стороны - пульт обеспечивает нужное для нас поведение телевизора. В данном примере именно пульт является таким стандартным средством доступа к телевизору. Можно даже сказать, средством доступа, не зависящим от конкретной модели телевизора - вспомните об универсальных пультах и о том, как отключаете звук надоедливой рекламы на экране в вагоне поезда, используя КПК!
В том же примере с телевизором у нас впервые промелькнуло слово интерфейс. И не случайно промелькнуло: именно так называют тот самый стандартный способ доступа к объекту. Более строго, интерфейс - это логическая группа открытых ( public ) операций объекта. Один и тот же объект может иметь несколько интерфейсов. У телевизора, например, их два - пульт ДУ и кнопки на корпусе. А может и больше - вспомните о возможности управлять бытовой техникой с помощью КПК или универсального пульта ДУ.
Кстати, посмотрите внимательнее на пульт ДУ или на экран программы удаленного контроля. Что вы видите - кнопки? Или кнопки, сгруппированные по функциональному признаку? Да, именно так: кнопки, переключающие каналы, расположены отдельно, рядом - группа кнопок, отвечающих за регулировку громкости звука, рядом - группа программируемых кнопок, и т. д. В принципе, можно сказать, что пульт реализует не один, а несколько интерфейсов - по числу функциональных групп кнопок. Впрочем, это уже формализм: мы просто хотели проиллюстрировать слова "логическая группа" в определении интерфейса.
Однако интерфейс - это не только и не столько группа операций объекта. Интерфейс отражает внешние проявления объекта, показывает, каким образом осуществляется взаимодействие с ним, скрывая остальные детали, не имеющие отношения к процессу взаимодействия.
Интерфейс всегда реализуется некоторым классом, который в таком случае называют классом, поддерживающим интерфейс. Как мы уже говорили ранее, один и тот же объект может иметь несколько интерфейсов. Это означает, что класс этого объекта реализует все операции этих интерфейсов. К данному моменту в голове читателя может созреть вопрос: "Мы же, вроде бы, говорили о классах и объектах, а теперь вдруг перешли на интерфейсы. Да и вообще, используются ли они в практике программирования или являются просто изящной теоретической конструкцией?". Ответ на этот вопрос прост: многие из существующих технологий программирования (например, COM, CORBA, Java Beans) не только активно используют механизм интерфейсов, но и, по сути, полностью основаны на нем.
Что ж, наверное, пришло время поговорить о том, как интерфейс изображается на диаграммах. Изображаться он может несколькими способами. Первый и самый простой из них - это класс со стереотипом <<interface>> (рис. 3.3):
Этот способ хорош, если нужно показать, какие именно операции предоставляет интерфейс. Если же такие подробности в данный момент не важны, предоставляемый интерфейс изображают в виде кружочка или, как говорят, "леденца" ( lollipop ) (рис. 3.4):
Обратите внимание на маленький значок на закладке папки ConduitSet. Это обозначение подсистемы, мы могли бы не рисовать его, а просто использовать стереотип <<subsystem>>. Впрочем, об этом мы еще поговорим.
И наконец, еще один способ изображения интерфейса. Он не является альтернативой описанным ранее способам, а используется для изображения интерфейсов, требующихся объекту для выполнения его работы. Обозначается он очень простым и логичным символом. Впрочем, судите сами (рис. 3.5):
Наблюдательный читатель уже, наверное, заметил, как логически совмещаются символы предоставляемого и требуемого интерфейсов.
Действительно, на диаграммах довольно часто можно увидеть такую картинку (рис. 3.6):
Да, кстати, вы заметили, что названия интерфейсов начинаются с буквы I? Эта традиция пошла из языка Java, и, как показывает практика, она весьма облегчает жизнь, если нужно, например, быстро разобраться в сложной диаграмме, составленной другим человеком.