Uploaded by bafini4617

Объектно-ориентированное программирование, лекции

advertisement
Объектно-ориентированный анализ и программирование
1
Объектно-ориентированный анализ и
программирование
Рабочая программа по дисциплине
Цели и задачи освоения дисциплины
Дисциплина «Объектно-ориентированный анализ и программирование» относится к группе наиболее важных
курсов для подготовки профессионала в области информационных технологий. Практически все сферы
деятельности современного специалиста требуют понимания принципов построения программных систем.
Именно
поэтому
целью
освоения
дисциплины
является,
прежде
всего,
знакомство
с
объектно-ориентированной парадигмой программирования, использование которой на сегодняшний день
является наиболее эффективным подходом к разработке программных структур и комплексов. Достижение
цели осуществляется посредством решения следующих задач.
• Знакомство обучающихся с терминологией объектно-ориентированного программирования.
• Овладевание базовыми принципами объектно-ориентированной парадигмы.
• Получение навыков в области проектирования компонентов программных систем с помощью
специализированного языка графического описания UML.
• Усвоение принципов работы объектно-ориентированного языка программирования Java.
• Знакомство с типовыми задачами проектирования и методами их решения с помощью аппарата объектов.
В результате изучения дисциплины обучающийся должен:
• Знать:
• Базовые понятия и принципы объектно-ориентированного программирования.
• Уметь:
• Использовать принципы объектно-ориентированной парадигмы для разработки эффективных структур
программных систем.
• Разрабатывать программы, использующие аппарат объектов.
• Владеть:
• Навыками, необходимыми для проведения объектно-ориентированного анализа и проектирования.
• Навыками, необходимыми для разработки компонентов программных систем с помощью
инструментальных сред графического проектирования.
Место дисциплины в структуре ООП ВПО
Дисциплина относится к общепрофессиональному циклу подготовки бакалавра и специалиста. Для освоения
материала необходимы знания, полученные в ходе изучения курса «Программирование». В свою очередь
дисциплина является базовой для таких предметов как «Управление жизненным циклом информационных
систем»,
«Моделирование
бизнес-процессов»,
«Проектирование
«Проектирование систем электронных коммуникаций».
систем
управления
знаниями»
и
Объектно-ориентированный анализ и программирование
2
Компетенции студента (общекультурные (ОК) и профессиональные (ПК,
СПК)), формируемые в результате освоения дисциплины
Компетенция
Сфера применения компетенции
Владеет культурой мышления, способен к обобщению, анализу, восприятию информации,
Общекультурная, профессиональная и
постановке цели и выбору путей её достижения (ОК—1)
научно- исследовательская
Способен к саморазвитию, повышению своей квалификации и мастерства (ОК-9)
Профессиональная и
научно- исследовательская
Осознает сущность и значение информации в развитии современного общества; владеет
Профессиональная и
основными методами, способами и средствами получения, хранения, переработки
научно- исследовательская
информации (ОК-12)
Способен осуществлять планирование и организацию проектной деятельности на основе
Профессиональная
стандартов управления проектами (ПК-16)
Умеет проектировать архитектуру электронного предприятия (ПК-17)
Профессиональная
Структура и содержание дисциплины
Содержание разделов учебной дисциплины
Наименование раздела дисциплины
Введение
Содержание раздела
Объектно-ориентированная парадигма. Окружающий мир как совокупность объектов.
Абстрагирование.
Тема 1. «Основы
Классы и объекты. Члены класса. Поля и методы. Конструкторы и деструкторы. Наследование.
объектно-ориентированного
Инкапсуляция. Полиморфизм. Статическое и динамическое связывание. Переопределение
программирования»
методов. Виртуальный метод. Абстрактный класс. Интерфейс. Вложенные и анонимные классы.
Тема 2. «Язык графического описания
Понятие UML. История возникновения и развития UML. Типы диаграмм. Структурные и
UML»
поведенческие диаграммы. Диаграмма классов. Связи между классами.
Тема 3. «Язык программирования
Язык программирования Java. Особенности. История развития. Виртуальная java-машина. Запуск
Java»
программ. Простейшая программа на языке Java. Типы данных. Массивы и коллекции.
Наследование и интерфейсы. Область видимости элементов. Статические члены. Сериализация.
Тема 4. «Шаблоны проектирования»
Понятие шаблонов проектирования. Низкоуровневые и высокоуровневые шаблоны. Основные,
порождающие, структурные и поведенческие шаблоны. Концепция МУС.
Учебно-методическое и информационное обеспечение учебной
дисциплины
Основные профессиональные сообщества, обсуждающие темы курса, в которых
можно задать вопросы экспертам и получить квалифицированный ответ:
Объектно-ориентированный анализ и программирование
Название
3
Раздел
Ссылка на раздел
Ведущие эксперты
CyberForum Объектно-ориентированное программирование и паттерны [1] Модераторы
Iguania
Объектно-ориентированное программирование
Модераторы
[2]
CyberForum UML
[3] Модераторы
UML2
Форум
[4] Модераторы
JavaTalks
Основы языка Java
[5] Модераторы
OODesign
Форум
http://forum.oodesign.com/ll Модераторы
Основные открытые и закрытые источники интернета, на которых есть самые
качественные материалы по темам:
Название
Раздел
Объектно-ориентированный анализ и
Ссылка на раздел
Шаблоны. Статьи по стандартам
http://ooad.asf.ru/Index.
проектирование
aspx
Habrahabr
Java. ООП. Проектирование и рефакторинг. UML Design
http://habr ahabr.ru
Сообщество системных аналитиков
Статьи
http://www.uml2.ru/index.
php
Javenue
Объектно-ориентированное проектирование
[6]
RSDN
Паттерны проектирования
[7]
Шаблоны проектирования
Справочник
http://www. design-pattern,
ru/
Intuit
Программирование на Java
[8]
INTERFACE
Введение в программирование на языке Java
[9]
Изучаем Java
Java-самоучитель
[10]
МФТИ\¥
Объектно-ориентированное программирование
[H]
Введение в объектно-ориентированное программирование
[12]
Лаборатория юного линуксойда
на Python
Codenet
Три кита ООП
[13]
Основная литература
№
Наименование
Автор (ы)
Год и место издания
н/п
1.
2.
Используется при изучении
разделов
Язык программирования Java
[14]
Java для Internet в Windows и Linux [14]
Баженова И.Ю.
Дунаев С.
М.: Диалог-МИФИ,
Язык программирования Java.
2008
Шаблоны проектирования
М.: Диалог-МИФИ,
Язык программирования Java
2004
3.
Объектно-ориентированные методы.
Грэхем И.
Принципы и практика
4.
Язык UML. Руководство пользователя
Буч Г., Рамбо Д.,
Издательский дом
Основы объектно-ориентированного
«Вильямс», 2004
программирования
Спб.: Питер, 2004
Язык графического описания UML
Издательский дом
Язык программирования Java.
«Вильямс», 2007
Шаблоны проектирования
Джекобсон А.
5.
Полный справочник по Java. Java SE 6 Edition
Шилдт Г.
Объектно-ориентированный анализ и программирование
6.
4
Приёмы объектно-ориентированного
Гамма Э., Хелм Р.,
проектирования. Паттерны проектирования
Джонсон Р., Влиссидс
Спб.: Питер, 2004
Шаблоны проектирования
д.
Дополнительная литература
№
п/п
1.
Наименование
Автор(ы)
Основные концепции и механизмы
Пышкин Е.В.
объектно- ориентированного программирования
Год и место
издания
Используется при изучении разделов
Спб.:
Основы объектно-ориентированного
БХВ-Петербург,
программирования
2005
2.
Java 2. Тонкости программирования. Том 1
Хорстманн К., Издательский дом
Корнелл Г.
3.
Java 2. Тонкости программирования. Том 2
Хорстманн К., Издательский дом
Корнелл Г.
4.
5.
Искусство программирования на Java
Шаблоны проектирования в JAVA. Каталог
Шилдт Г.,
Издательский дом
«Вильямс», 2005
Гранд М.
Издательский дом
«Вильямс», 2004
проиллюстрированных при помощи UML
6.
Язык программирования Java
«Вильямс», 2007
Холмс Д.
популярных шаблонов проектирования,
Язык программирования Java
«Вильямс», 2007
Язык программирования Java
Язык графического описания UML. Язык
программирования Java. Шаблоны
проектирования
Применение UML 2.0 и шаблонов проектирования Ларман К.
Издательский дом
Язык графического описания UML.
«Вильямс», 2006
Шаблоны проектирования
Программное обеспечение
• StarUML или VioletUML,
• Oracle JDK (версии не ниже 1.6),
• Eclipse, IntelijIDEA, NetBeans или Sublime Text 2,
• MS Word или Libre Office Writer.
Содержание курса
Введение
Мышление человека в целях улучшения восприятия окружающего мира склонно к построению абстракций.
Одним из типов таких абстракций является объект. Система идей и понятий, основанная на таком
представлении окружающей действительности получила название объектно-ориентированной парадигмы.
Неформально объект может быть определен как некоторая сущность, обладающая важными в контексте
решаемой задачи свойствами. Кроме того, любая подобная сущность имеет свое, ни от кого не зависящее,
внутреннее состояние, способна посылать сообщения другим сущностям и, в свою очередь, изменять
поведение в соответствии с полученной информацией. Такой подход к проектированию оказался чрезвычайно
удобным и наглядным, так как, по большому счету, весь окружающий мир может быть просто и понятно
описан в терминах объектов.
Цель данного пособия, как было сказано выше, — познакомить обучающихся с объектно-ориентированной
парадигмой программирования, попытаться в общих чертах донести ее основные принципы, а также показать
некоторые примеры архитектурных задач и способы разработки подходящих решений с помощью аппарата
объектов,
продемонстрировать
«выразительную
мощность»
изучаемого
подхода
применительно
к
Объектно-ориентированный анализ и программирование
проектированию программных систем.
Предлагаемый материал излагается исходя из предположения, что обучающийся владеет хотя бы одним
процедурным языком программирования (например, Pascal или ANSI С). Это значит, что часть терминов, уже
знакомых «начинающему» разработчику используется без объяснения. В случае затруднений с пониманием
рекомендуется «освежить» в памяти базовые знания по программированию.
Остается добавить, что сам по себе предмет является весьма сложным и объемным (студенты профильных
направлений изучают его не менее года), что затрудняет его изложение, поэтому замечания по структуре и
содержанию пособия приветствуются.
Тема 1. «Основы объектно-ориентированного программирования»
Данная тема представляет собой своеобразное введение в объектно-ориентированное программирование. Цель
этого раздела — на простых примерах пояснить основные определения объектной парадигмы.
Классы и объекты
Центральным понятием объектно-ориентированного программирования является класс. Классом принято
называть абстракцию, включающую в себя важные в контексте решаемой задачи аспекты некоторой сущности.
Такие аспекты также еще называют членами класса.
К членам класса относятся поля (свойства) и методы (процедуры и функции). Поля соответствуют данным, а
методы — некоторым действиям (например, действиям по обработке данных). Таким образом, класс может
быть определен как именованная совокупность полей и методов.
Иными словами, класс — это некоторый универсальное описание чего-либо.
Смежным с классом понятием является объект. Объект — это экземпляр класса. Один класс в общем случае
может иметь произвольное число экземпляров — конкретных реализаций. В большинстве случаев именно
конкретными реализациями и оперирует программист во время своей работы.
Поясним сказанное на примере. Рассмотрим класс Plane — абстракцию самолета. К свойствам класса могут
быть отнесены модель самолета, метка (сокращение, используемое диспетчерами), количество мест для
пассажиров, число членов экипажа, грузоподъемность, крейсерская скорость и так далее. К методам — взлет,
посадка, выпуск шасси, разворот влево, разворот вправо, снижение, набор высоты и так далее. Схематично
вышесказанное представлено на рисунке.
5
Объектно-ориентированный анализ и нрофаммирование
6
Членом класса может быть другой класс. Например, класс Auto
(автомобиль)
может
содержать
ноле
Engine
(мотор),
представляющее собой ссылку на экземпляр другой сущности.
Говоря языком структурного программиста, поле класса — это
ссылка
на
экземпляр
переменную,
класса,
а
массив,
метод
—
структуру
это
или
другой
последовательность
инструкций для манипулирования данными. Объект же —
конкретный экземпляр класса. Если класс — это своеобразный
«чертеж», то объект — это воплощение сущности в памяти
ЭВМ по этому чертежу.
Plane
model
label
passangerCount
crewCount
capacity
speed
rise
landing
gearselection
Здесь уместно упомянуть о двух особенных методах —
конструкторе
создания
и деструкторе.
объекта.
Как
Первый
правило,
в
предназначен
него
для
включаются
инструкции, которые необходимо выполнить до начала работы
,e ft
Поля
Методы
right
up
down
с объектом (например, инициализация полей). В большинстве
объектно-ориентированных языков с вызова конструктора и
Структура класса Plane
начинается создание объекта в памяти ЭВМ. Деструктор же
вызывается при уничтожении объекта, и, как правило, содержит завершающие инструкции (например,
освобождение системных ресурсов).
Подведем промежуточный итог.
• Класс — фундаментальное понятие объектно-ориентированного программирования.
• Класс — это совокупность членов (полей и методов, или данных и операций над ними).
• Объект — конкретная сущность в памяти ЭВМ (экземпляр класса).
Три принципа объектно-ориентированного программирования
Рассматриваемые в данном разделе принципы крайне важны. Постарайтесь понять их как можно лучше. В
некотором роде они являются «гремя китами» объектно-ориентированного программирования.
Наследование
Работа с классами была бы невозможной без такого понятия как наследование.
Восприятие человеком окружающего мира «многоступенчато». Например, любой человек — это, прежде всего,
человек. Он имеет характерные для каждого признаки: имя, возраст, родителей, — и способен совершать
определенные действия: двигаться, говорить, улыбаться и так далее. С другой стороны, например, школьник
имеет свои признаки: класс, расписание, дневник, — и действия: выполнить домашнее задание или вымыть
доску. Но в то же время школьник — тоже человек. Он точно также имеет имя, возраст и так далее.
Объектно-ориентированный анализ и профаммирование
Принцип
наследования
позволяет
7
удобно
оперировать подобными сущностями: для работы с
классами Human (человек) и Schoolboy (школьник)
нс потребуется описание двух классов, содержащих
повторяющиеся члены. Второй класс может быть
объявлен «наследником» первого, более общего. В
таком случае он автоматически унаследует все
элементы родительского класса (если они, конечно,
Human
Schoolboy
name
class
age ------------ ----- schedule
parent
diary
doHomework
move
----washBlackboard
speak
smile
не будут специально скрыты). Схематично такая
Наследование классом Schoolboy от класса Human
ситуация представлена на рисунке.
Таким образом, наследование — это механизм
объектно-ориентированного программирования, позволяющий классу-потомку расширять функциональность
родительского класса, заимствуя при этом свойства и методы.
Класс-родитель еще принято называть «суперклассом». Класс-потомок сам может являться суперклассом.
Родитель может иметь несколько потомков. Потомок, в общем случае, — несколько родителей (такая
ситуация называется множественным наследованием). Эти факты позволяет конструировать сложные
древовидные иерархии. Подобный пример представлен на рисунке.
Здесь
Transport
средства.
—
Классы
абстракция
транспортного
AutoTransport (автомобильный
транснорт), AirTransport (воздушный транспорт) и
WaterTransport
(водный
транспорт)
Т ransport— WaterT ransport
AutoTransport ^ A irT ra n s p o rt
расширяют
Transport. В свою очередь классы Auto и Moto
соответствуют
абстракциям
автомобиля
и
мотоцикла.
Важно
Auto
понимать,
большую
гибкость
что
наследование
процессу
придает
Moto
Пример древовидного наследования
проектирования
сложных систем, так как практически все языки программирования предоставляют явные и неявные методы
для вызова из класса-потомка методов класса-родителя. Примером явного обращения служить использование
ключевого слова для ссылки на родителя. Примером неявного — обращение к классу-родителю при вызове
непереопределенного метода (речь о переопределении пойдет ниже).
Здесь необходимо также упомянуть о широко распространенной в объектно-ориентрованных языках
концепции единого базового класса. Дело в том, что класс, не имеющий явного родителя, все равно наследует
от некоторого класса: в Java —от java.lang.Object, в Eiffel —от ANY, в Delphi —от TObject.
Инкапсуляция
Вторым важным принципом объектно-ориентированного профаммирования является инкапсуляция. Такое
название получил языковой механизм, позволяющий ограничивать доступ к компонентам профаммной
системы.
Общее правило таково: если компоненту нет необходимости знать что-либо о внутреннем устройстве
другого элемента, то он не должен это знать. Иными словами, поля и методы, доступ к которым не
требуется другой сущности для работы, должны быть скрыты от нее. Основной целью инкапсуляции является
достижение согласованного состояния элементов. Запрет доступа к определенным членам класса позволяет
контролировать изменения и прогнозировать результат некоторых дейст вий.
Рассмотрим
классический
пример
нарушения состояния объекта.
Пусть имеется
некоторый
класс
DoubleValue, содержащий два поля — value и stringRepresentation. Экземпляры данной сущности позволяют
Объектно-ориентированный анализ и программирование
8
хранить вещественное число и его строковое представление. Если оба поля являются доступными, то
возможна ситуация, когда кто-либо изменить значение одного поля и забудет изменить соответствующим
образом другое. Подобное нарушение согласованности может привести к «снежному кому» ошибок.
Правильным решением в описанной ситуации является сокрытие полей и предоставление трех методов:
setValue (для изменения вещественного значения и построения нового строкового представления), getValue
(для получения вещественного значения) и getStringValue (для получения строкового представления).
Для лимитирования доступа к членам класса применяется механизм, получивший название области
видимости. Его суть заключается в том, что при объявлении члена класса допускается явно указывать
ограничивающий доступ модификатор. Типичными примерами таких модификаторов являются public
(элемент виден всем без исключения) и private (элемент виден только внутри класса).
Ряд объектных языков программирования, помимо прочего, предоставляет возможность организации
дружественного доступа. Поясним, что понимается под этим термином. Дружественным доступом называется
механизм, позволяющий методам одного класса получать доступ к скрытым членам другого класса. Как
правило, дружественный доступ не передается по наследству и не транзитивен («никто не обязан дружить с
друзьями родителей» и «друг моего друга не обязательно мой друг»).
Необходимо отметить, что термин «инкапсуляция» имеет также другое, менее распространенное, значение.
Инкапсуляция — это еще и языковая конструкция, позволяющая объединять данные с методами,
обрабатывающими их.
П олиморфизм
Третьим принципом объектно-ориентированного программирования является полиморфизм. Под этим
термином принято понимать возможность элементов с одинаковой спецификацией иметь различные
реализации.
Типичным примером полиморфизма является наследование. Если некоторый класс содержит определенный
метод, то и его класс-потомок будет содержать аналогичный метод. Его вызов, при условии, что он не
переопределен в потомке, будет передан на обработку родительскому классу. Иначе (когда метод
переопределен)
обработкой
будет
заниматься
класс-потомок.
Такой
подход
позволяет
создавать
единообразный и простой для понимания программный код со сложным поведением.
Можно сказать, что полиморфизм — это существование точек «кастомизации», когда один и тот же код
может означать разные операции в зависимости от дополнительных условий.
Частным случаем полиморфизма является перегрузка методов. Так называется ситуация, когда класс
содержит несколько членов с одинаковым именем, но разной сигнатурой. Тогда выбор конкретной реализации
будет зависеть от набора передаваемых параметров.
Одним из основных механизмов, лежащих в основе полиморфизма, выступает динамическое связывание. В
процедурных языках тело функции связывается с местом ее вызова на этапе компиляции. Такой подход
получил название статического (или раннего) связывания. В объектных языках же возможна ситуация, кода
указатель на экземпляр класса А на самом деле связан с экземпляром класса В (при этом В является
наследником А). Вызов перегруженного метода через указатель должен повлечь работу метода из В, однако
компилятору это не известно. Решением проблемы является динамическое (или позднее) связывание, то есть
привязка тела метода к месту вызова непосредственно во время выполнения.
Наиболее популярным способом организации динамического связывания является аппарат виртуальных
функций. Виртуальным называется метод класса, который может быть переопределен в потомке таким
образом, что конкретная реализация будет выбираться во время выполнения.
Технически работа с виртуальными функциями осуществляется через специальные таблицы. Каждый объект,
имеющий хотя бы одну виртуальную функцию, хранит указатель на таблицу своего класса, с помощью которой
Объектно-ориентированный анализ и программирование
9
вычисляется адрес расположения необходимой реализации метода в памяти.
Остается добавить, что использование виртуальных функций актуально не для всех языков программирования.
Так, например, в Java нет понятия виртуальной функции, а в Python — все методы являются виртуальными.
Прочие понятия объектно-ориентированного программирования
Абстрактные классы
Абстракт ным называется класс, который не может иметь экземпляров. Таким образом, такой класс позволяет
перейти на еще более высокий уровень абстрагирования. Обычно они применяются для описания самых
общих свойств сущностей.
Абстрактный класс, точно также как и обычный класс, может содержать поля и методы. Если метод
объявляется как абстрактный, то он обязательно должен быть переопределен в потомке. Некоторые языки
программирования допускают наличие в абстрактном классе только абстрактных методов, другие —
разрешают
не
только
декларировать,
но
и
реализовывать
часть
функциональных
возможностей
непосредственно в абстрактной сущности.
Фактически, абстрактные классы реализуют на практике принцип полиморфизма.
Также необходимо отметить, что все-таки не существует устоявшегося понятия «абстрактного» класса.
Нюансы
работы
с
подобными
сущностями
тесно
связаны
с
особенностями
конкретного
языка
программирования. Так, например, несмотря на общепринятое определение, в Delphi разрешается создавать
экземпляры абстрактных классов, но нс разрешается обращаться к их абст рактным методам.
Интерфейсы
Альтернативой наследованию являются интерфейсы — семантические и синтаксические конструкции,
специфицирующие функциональные возможности, предоставляемые сущностями. Проще говоря, интерфейс
— это совокупность сигнатур методов, которые должен реализовывать класс.
Данный механизм появился и развивался как способ решения проблем, возникающих при множественном
наследовании. В большинстве объектно-ориентированных языков программирования класс, реализующий
интерфейс, должен реализовывать все методы объявленные в этом интерфейсе. При этом обращаться к
экземпляру класса в таком случае можно по имени интерфейса. Допускается ситуация, когда один класс
реализует несколько интерфейсов. В таком случае обращаться к экземпляру можно по имени любого из
интерфейсов.
Поясним изложенное на примере. Пусть
WritingToo
Pen
Write
write
Pencil
имеется
абстрагирующее
ExpensivePen------Refill
refill
Пример использования интерфейсов
WritingTool,
пишущие
средства.
Любой экземпляр данного класса должен
справляться со своей основной функцией
—
CheapPen
класс
письмом,
например,
но
и с
писать
можно еще,
помощью клавиатуры.
Поэтому целесообразно вынесли метод
write в отдельный интерфейс Write. Пусть
теперь
класс WritingTool
имеет двух
наследников — ручку и карандаш (классы
Реп и Pencil). Так как родительский класс реализует соответствующий интерфейс, то и потомки имеют доступ
к методу write. От Реп наследуют еще два класса — ChcapPen и ExpcnsivePen (дешевая и дорогая ручка). Они
отличаются тем, что дорогая ручка имеет сменные стержни или может быть перезаправлена. Но существуют
также и перезаправлясмые маркеры. Поэтому данный функционал целесообразно вынести в отдельный
Объектно-ориентированный анализ и программирование
интерфейс Refill. Схематично описанная ситуация представлена на рисунке.
Вложенные классы
Вложенным классом называется класс, объявленный внутри другого класса. Не стоит путать понятия
«вложенный класс» и «класс-член». В первом случае описание класса размещается внутри другого класса, во
втором же — внутри класса размещается ссылка на другой класс.
Обычно экземпляр вложенного класса может быть создан только внутри класса-хозяина (или управляющего
класса). В некотором роде такой подход реализует принцип инкапсуляции — сторонние классы даже не будут
знать о существовании вложенного класса.
Данный механизм, как правило, применяется в тех случаях, когда необходимо оперировать сущностью только
в одном месте, а с точки зрения архитектуры приложения, она не может быть «внедрена» в управляющий
класс.
Анонимные классы
Анонимным называется неименованный класс, то есть такой класс, экземпляр которого создается «здесь и
сейчас». Так как подобный класс не имеет имени, то его экземпляр не сможет быть создан в другом участке
кода. На самом деле способ создать второй экземпляр класса все же имеется (например, шаблон
проектирования «прототип»), но на практике это не нужно (иначе нет смысла создавать анонимный класс
вовсе).
Чаще всего анонимные классы применяются для создания сущностей с уникальным поведением. Например,
при создании графического интерфейса посредством swing часто требуется реализовать уникальный класс для
обработки конкретного действия пользователя. В таком случае анонимный класс — наиболее подходящее
решение.
Пространства имен
При разработке сложных программных систем одним из эффективных способов организации кода является
группировка элементов в соответствии с их назначением. Как правило, эта задача решается с помощью
разделения пространства имен.
Пространство имен — это некоторый структурный контейнер, включающий в себя программные единицы.
Важной особенностью при этом является тот факт, что единицы, расположенные в одном пространстве имен
известны друг другу.
Такое разделение бывает полезно в тех случаях, когда проект содержит два или более классов с одним именем
и разной функциональностью. При обращении к такому объекту по имени управление будет передано
экземпляру класса, расположенному в том пространстве имен, из которого был выполнен вызов. Доступ к
классу с аналогичным названием в другом пространстве возможен только при явном указании имени этого
пространства.
Типичными для современных языков уровнями иерархии пространств имен являются блок кода, метод, класс,
модуль (пакет) и глобальное адресное пространство.
10
Объектно-ориентированный анализ и программирование
Контрольные вопросы по Теме 1
1. В чем отличие между классом и объектом?
2. Что понимается под членами класса?
3. Как Вы понимаете термины «наследование», «инкапсуляция», «полиморфизм»?
4. Как называется механизм, используемый для ограничения доступа к членам класса? На чем основана его
работа?
5. Что такое «перегрузка» методов?
6. Чем отличаются раннее и позднее связывания?
7. Как с технической точки зрения работает механизм виртуальных функций?
8. В чем отличие абстрактного класса от интерфейса?
9. В чем отличие вложенного класса от анонимного?
10. Как Вы понимаете термин «пространство имен»?
Выводы после Темы 1
• Центральными понятиями объектно-ориентированного программирования являются класс и объект.
• Класс — «чертеж», который описывает некоторую абстрактную сущность.
• Объект — конкретный экземпляр класса в памяти ЭВМ.
• Три базовых принципа объектно-ориентированного программирования:
• Наследование — класс может передать свои свойства и методы «по цепочке» следующему,
расширяющему функционал, классу.
• Инкапсуляция — ни один класс не должен иметь доступа к «лишней» информации.
• Полиморфизм — один интерфейс, множество реализаций.
• Интерфейс — альтернатива множественному наследованию.
Тема 2. «Язык графического описания UML»
Управление процессом разработки любой программной системы осуществляется на основании подготовленной
на этапе проектирования документации. Известно, что подробное описание программной архитектуры даже
начального уровня сложности, как правило, занимает более сотни страниц. Такой объем информации
сказывается на восприятии непосредственным реализатором (программистом) задумок архитектора, что
значительно увеличивает сроки разработки.
В связи с этим в середине 80-ых годов прошлого века назрела серьезная проблема — используемые
методологии проектирования не справлялись с возрастающей сложностью систем, сопутствующие разработке
процессы замедлялись, весь процесс создания программных продуктов становился затянутым и менее гибким.
Все эти факты привели к осознанию потребности в едином, простом для восприятия языке описания
программных продуктов. Обобщение наиболее популярных на тот момент времени методологий (Booch,
Object-Oriented Software Engineering, Object Modeling Technique) привело к созданию языка моделирования
U M L (Unified Modeling Language), первоочередной целью которого было упрощение и унификация процесса
проектирования.
И
Объектно-ориентированный анализ и программирование
История развития языка UML
Резкое увеличение числа методов объектно-ориентированного проектирования и анализа при по прежнему
острой неудовлетворенности разработчиков ПО их возможностями привело к осознанию необходимости
создания единого и общепринятого метода проектирования. Разработка средства, получившего название UML,
велась в лаборатории компании Rational Software.
Первая версия (0.8) языка моделирования UML была выпущена спустя приблизительно год после начала
сотрудничества Г. Буча и Д. Рамбо (авторов методологий проектирования OODA и ОМТ соответственно,
сотрудников Rational Software), в 1995 году. Осенью 1995 года к разработке подключился автор метода OOSE
И. Якобсон.
Каждая из методологий, положенных в основу UML обладала своими достоинсвами и недостатками:
• OODA (Booch) была прежде всего ориентирована на дизайн программных систем, но не предоставляла
инструментов для их анализа;
• ОМТ включала инструментарий для анализа, но была практически непригодна для проектирования
сложных систем;
• OOSE применялась прежде всего в тех случаях, когда приоритетным являлось требование анализа
поведения систем.
UML объединил преимущества наиболее популярных подходов проектирования своего времени и
нивелировал их недостатки, в следствии чего эта новая методология практически сразу же стала стандартом
«де-факто» в области описания архитектур программных систем.
В результате растущего интереса к UML ведущая роль в разработке в 1996 году перешла к консорциуму OMG
(Object Managment Group). В этом же году были выпущены версии 0.9 и 0.91.
С 1997 года к разработке стандарта стали подключаться такие крупные корпорации как Microsoft,
Hewlett-Packard, IBM и Oracle Corporation. Итогом совместной работы гигантов программной индустрии стали
спецификации UML версии 1.0 и 1.1.
Версии 1.3, 1.4 и 1.5 были выпущены в период с 1999 по 2003 годы. Улучшения в основном касались
описательных возможностей нотации UML.
В 2005 году был выпущен расширенный и дополненный стандарт UML 2.0.
Последней версией на сегодняшний день является стандарт 2.4.
Структура UML
Унифицированный язык моделирования UML предназначен для описания программных систем. Создатели
данного инструмента преследовали три цели: разработать средство для спецификации ПО любого уровня
сложности (от простого графического или консольного приложения до web-сервера с обработкой запросов в
реальном времени), проассоциировать данное средство с реальными языками программирования и
предоставить простой способ для создания документации на ПО. Таким образом, сами авторы определили
UML как средство для специфицирования, конструирования и документирования ПО. Однако разработанное
средство получилось настолько удачным, что оказалось пригодно для спецификации, описания и анализа не
только программных систем, но и практически любых объектов и процессов. Так, например, UML широко
применяется для описания бизнес-процессов.
Стандарт UML использует графические элементы (диаграммы) для отображения информации о моделируемом
объекте. Диаграмма представляет собой набор вершин и дуг, объединяющих их. Вершины также
называются сущностями, а дуги — связями. Выделяют четыре типа сущностей (структурные, поведенческие,
группирующие и аннотирующие) и четыре типа связей (зависимость, ассоциация, обобщение и релизация).
Хотя некоторые из них и будут рассмотрены в следующем разделе, тем не менее крайне рекомендуется
обратиться за дополнительной информацией к первоисточнику — книге Г. Буча, Д. Рамбо и И. Якобсона
12
Объектно-ориентированный анализ и программирование
«Язык UML. Руководство пользователя». Каждая диаграмма является или структурной (описывают сущности),
или поведенческой (описывают деятельности). Основные типы диаграмм с их краткой характеристикой
перечислены ниже.
• Диаграмма классов (class diagram). Предназначена для изображения структур классов и их взаимосвязей.
Подробно будет рассмотрена ниже.
• Диаграмма компонентов (component diagram). Структурная диаграмма, предназначенная для отражения
связей между компонентами системы. Особое внимание уделяется интерфейсам, «склеивающим» блоки в
цельное представление. В основе данного типа диаграмм лежит понятие «порт». Порт — это окно в
инкапсулированный компонент. Совокупность входных и выходных портов образует интерфейс
компонента. Допускается изображение компонента как в виде единой сущности, так и в виде композиции
частей.
• Диаграмма составной структуры (composite structure diagram). Предназначены для отображения
внутренней структуры классов и взаимодействия их элементов. Как правило, используются совместно с
диаграммами классов.
• Диаграмма развертывания (deployment diagram). Иногда также называются диаграммами размещения.
Используются для моделирования физических аспектов объектно-ориентированных систем. Позволяет
выполнять анализ работы с учетом аппаратных особенностей размещения программных модулей: топологии
узлов, типов межузловых коммуникаций, и так далее. Отражает расположение программных блоков на
узлах. В некотором роде является разновидностью диаграммы классов, фокусирующей внимание на
аппаратуре. На практике используется крайне редко (в основном при разработке специфического ПО).
• Диаграмма объектов (object diagram). Применяются при моделировании сущностей классов —объектов.
Позволяют получить и проанализировать «снимок» системы в конкретный момент времени. Серия
диаграмм классов может быть полезна при моделировании определенного сценария поведения системы.
Вплотную соотносится с диаграммой классов (как частное с общим).
• Диаграмма пакетов (package diagram). При моделировании сложных систем неизбежно приходится
работать с большим числом классов. При масштабировании таких систем возникает потребность в
укрупнении элементов. Таким «укрупненным» блоком как раз и является пакет — совокупность классов.
Элементами пакета являются близкие по своей семантике элементы. Диаграмма пакетов чаще всего
применятся при моделировании глобальных свойств системы.
• Диаграмма профилей (profile diagram). Введена в UML 2.2, до этого заменялась композицией диаграмм
других типов. Оперирует понятиями на уровне метамодели объекта анализа. Отражает связь между
пакетами и стереотипами.
• Диаграмма деятельности (activity diagram). Применяется для моделирования динамических аспектов
программных систем. Под деятельностью понимается структурированное описание текущего поведения.
Представляет собой описание последовательности шагов вычислений. Имеет много общего с традиционной
блок-схемой, но в отличие от блок-схем стандарта 19.701-90 позволяет наглядно изображать параллелизм.
• Диаграмма состояний (state machine diagram). Отображает поток управления от одного состояния к
другому внутри объекта. Чаще всего применяется при моделировании реактивных систем. Позволяет
изображать динамические аспекты моделируемой системы в виде конечного автомата.
• Диаграмма прецедентов (use case diagram). Известна также как диаграмма вариантов использования.
Основной вид диаграмм для моделирования и анализа поведения системы в целом. В основе лежит понятие
«прецедента» — действия, разрешенного в текущий момент времени с учетом текущего состояния системы,
приводящего к желаемому результату. Фактически, позволяют описывать всевозможные цепочки
воздействий, оказание которых на систему, является допустимым.
• Диаграмма коммуникации (communication diagram). До UML 2.0 называлась также диаграммой
кооперации. Используется для моделирования динамических аспектов системы. Позволяет изображать
«раскадровку» поведения. Подчеркивает структурную организацию объектов, обменивающихся
13
Объектно-ориентированный анализ и программирование
14
сообщениями.
• Диаграмма последовательности (sequence diagram). Применяется для моделирования и анализа динамики
развития системы. Схожа с диаграммой коммуникации, но акцент сделан на порядке сообщений.
• Диаграмма обзора взаимодействия (interaction overview diagram). Позволяет изображать сложные
сценарии поведения. Состоит из блоков, описывающих конкретные последовательности действий. По своей
сути похожа на диаграмму пакетов, но вместо диаграмм классов преимущественно используются диаграммы
коммуникаций и последовательностей.
• Диаграмма синхронизации (timing diagram). Представляет собой разновидность диаграммы
последовательности, позволяет анализировать поведение объектов на определенном временном
промежутке. Главное отличие заключается в том, что оси времени и жизни объектов меняются местами.
Как следует из обзора существующих в UML типов диаграмм, наибольшая роль отводится диаграммам
классов. В большинстве своем остальные диаграммы являются или переработанными классовыми, или
вспомогательными и редко используются на практике. Посредством представленных выше типов диаграмм
может быть компактно и просто описана программная система любого уровня сложности.
Диаграммы классов
Основное назначение диаграмм этого типа — отображение набора классов, интерфейсов и связей между ними.
Наиболее важным элементом диаграммы является класс. Отдельный класс соответствует группе объектов,
имеющих общую структуру, и изображается в виде прямоугольника, поделенного с помощью горизонтальных
линий на три секции.
Обязательным элементом класса является его имя. Обычно имя класса помещается посередине первой секции
блока и выделяется полужирным начертанием. В случае изображения абстрактного класса при написании
имени используется также курсив. Кроме того, общепринятой практикой является изображение курсивом
всех элементов, имеющих отношение к абстрактному классу. Хотя данное требование и носит
исключительно
рекомендательный
характер,
лучше
все
же
следовать
ему.
Допускается
указание
принадлежности класса к пакету с помощью следующего шаблона.
<имя п а к е т а х <имя к л а с са >
Вторая секция блока класса содержит описание атрибутов. Отдельный атрибут записывается согласно
следующему шаблону.
< кван тор в и д и м о с ти х и м я а т р и б у т а х к р а т н о с т ь атр и б у та>
Квантор видимости может принимать одно из следующих значений.
• Знак «+» означает, что атрибут имеет область видимости public, то есть данный элемент виден всем без
исключения сущностям.
• Знак «-» означает, что атрибут имеет область видимости private, то есть данный элемент виден только
внутри методов данного класса.
• Знак «#» означает, что атрибут имеет область видимости protected, то есть данный элемент виден внутри
методов класса и наследующим сущностям.
Квантор видимости может быть опущен. Допускается непосредственное указание области видимости с
помощью ключевых слов public, private и protected.
Имя атрибута представляет собой простую текстовую метку, однозначно идентифицирующую член класса.
Допускается через символ «:» указывать также тип атрибута (например, integer, array, list или hashtable, double,
пользовательский класс и так далее).
Кратность атрибута необходима для указания возможного числа подобных элементов. Общепринятой
является следующая форма записи.
Объектно-ориентированный анализ и программирование
15
[<нижняя гр а н и ц а > . . < верхняя граница>]
Нижняя и верхняя Гранины задают диапазон, ограничивающий число элементов. Для указания бесконечного
количества применяется символ «*». Кратность атрибута может не указываться, в таком случае она по
умолчанию принимается равной одному.
Третья секция содержит описание методов класса. Каждый метод описывается согласно следующему шаблону.
< кван то р в и д и м о с ти х и м я м е то д а > (< п а р а м е тр ы > ): < возвращ аем ое зн а ч е н и е > { < с тр о к а -св о й с тв о > }
Назначение и допустимые (|юрмы квантора видимости метода совпадают с аналогичным полем атрибутов.
Данное ноле также может быть опущено.
Имя метода является текстовой меткой и служит для однозначной идентификации сущности. Поле параметров
содержит текстовое описание всех сущностей, передаваемых в метод. Каждая такая сущность записывается в
соответствии с шаблоном.
<вид п а р а м е тр а Х и м я п а р а м е т р а > :< выражение типа> = < значение по умолчаник»
Вид параметра — это одно из ключевых слов in (по умолчанию), out или inout. Имя параметра задает
текстовую
метку,
идентифицирующую сущность.
Выражение
тина
зависит от
конкретного
языка
про!раммирования. Значение ио умолчанию позволяет явно определить величину, передаваемую в
подпрограмму. Из всех перечисленных нолей обязательным является только имя переменной.
Поле возвращаемого значения метода, как следует из названия, используется для указания типа
возвращаемого значения.
Строка-свойство необходима для указания специфических особенностей метода. Так, например, в случае, если
метод содержит параллельные участки кода, принято использовать строку вида «{concurrency = concurrent}».
При описании класса разрешается опускать вторую и третью секции или третью секцию. Примеры валидных
диаграмм классов представлены на рисунке.
Figure
Circle
Object
+ radius
На первом фрагменте диаграммы
представлен
абстрактный
Figure с единственным методом для
getSquareQ: double
вычисления площади.
фрагменте
Geometry.Rectangle
- width: double
- height: double_______
+
♦
+
*
класс
EulerVenn
+ circles: Circle [□..’]
getWidthQ: double
getHeightQ: double
setWidth(width)
setHeight(height)
Примеры валидных диаграмм классов
На втором
представлен
содержащий
ни
класс,
атрибутов,
не
ни
методов, — соответствующие секции
блока
опущены.
На
третьем
фрагменте изображен класс Circle с
единственным
атрибутом.
В
четвертом примере внимание стоит
обратить на способ записи имени
класса (прямое указание на пакет) и
кванторы видимости атрибутов. В случаях, когда класс принадлежит пакету не верхнего уровня, допускается
явно указывать всю цепочку (например, Example.Geometry.Rectangle). Последний фрагмент примечателен тем,
что представленный класс включает в себя динамический массив классов Circle — используется поле
кратности атрибута (при этом допускается, что данный массив может быть пустым).
Изображение интерфейсов выполняется схожим образом, за тем исключением, что имя интерфейса (первая
секция блока) указывается в кавычках (например, «autocomplete») или с использованием ключевого слова
«interface» перед именем (например, interface autocomplete). Кроме того, секция атрибутов либо не
изображается вовсе (таким образом, описание интерфейса состоит лишь из двух секций), либо остается пустой.
Объектно-ориентированный анализ и программирование
Второй значимой составляющей диаграммы классов являются связи между классами и интерфейсами. Два
класса могут находиться друг с другом в следующих отношениях.
• Наиболее общим является отношение зависимости (dependency relationship). На схеме для обозначения
зависимости используется пунктирная линия со стрелкой на конце. Стрелка указывает направление от
зависимого класса к независимому. Допускается указание рядом со стрелкой одного или нескольких (через
запятую) ключевых слов (acces, bind, derive, import, refine), характеризующих отношение. При этом
используемое слово помещается в кавычки. Типичным примером отношения зависимости является
ситуация, когда изменение одного объекта влечет за собой изменение другого. Данное отношение является
наиболее общим и все остальные связи между сущностями, по сути, являются его частными случаями.
• Отношение ассоциации (association relationship) указывает на то, что между классами есть какое-либо
отношение. Примером простой ассоциации является связь между классами «преподаватель» и
«университет» (преподаватель работает в университете). На схеме подобное отношение отмечается с
помощью сплошной дуги со стрелкой. На концах дуги разрешается указывать кратность классов-участников
связи. Для указания кратности связи используются те же самые обозначения, что и для указания кратности
атрибута. В общем случае допускается использовать N -арную ассоциацию. Такая связь обозначается с
помощью присоединения к ромбу сплошными линиями классов-участников связи. Частным случаем
рассматриваемой связи является исключающая ассоциация.
• Отношение агрегации (aggregation relationship) применяется в тех случаях, когда один класс является
составной частью другого (часть-целое). На схеме такая связь обозначается с помощью сплошной линии с
ромбом на конце, причем ромб указывает на класс-целое. Данный тип отношения позволяет понять из
каких составных частей состоит система и отобразить иерархию компонентов.
• Отношение композиции (composition relationship) является частным случаем отношения агрегации и
используется в том случае, когда класс является составной частью другого класса и не может существовать
отдельно. Например, комната является частью здания и не может существовать сама по себе (иными
словами более общая часть композиции должна иметь квантор кратности 0..1, в то время как общее
агрегации может иметь любое значение квантора кратности). Графически отношение композиции
изображается точно также, как и отношение агрегации, но с закрашенным ромбом. Допускается заменять
композицию агрегацией. Данный тип отношений на практике чаще всего применяется для декомпозиции
системы.
• Отношение обобщения (generalization relationship) применяется для отображения иерархических
отношений между классами. Наиболее простой пример такого отношения — связь между
классом-родителем и классом-потомком. На диаграмме классов такая связь показывается в виде сплошной
линии с треугольником на конце. Треугольник при этом указывает на более общий класс (в предыдущем
примере таким классом будет родитель). Допускается объединение стрелок при изображении связей между
родителем и несколькими потомками. Рядом с треугольником обобщения разрешено размещать
пояснительный текст (например, ограничения вида {complete}, {disjoint}, {incomplete}, {overlapping} и так
далее).
Кроме того, любая связь может иметь текстовую метку — имя. Данная метка должна однозначно
идентифицировать отношение и быть уникальной в пределах схемы.
Примеры отношений между классами представлены на рисунке.
16
Объектно-ориентированный анализ и прелраммирование
17
Класс Circle является наследником
абстрактного
Диа>рамма
класса
Figure.
Эйлера-Вена
может
включать в свой состав произвольное
число
окружностей.
При
этом
наиболее важным методом является
prinlDiagram,
входящий
в состав
интерфейса Print, который позволяет
вывести
диаграмму
EulerVenn
Print.
на
реализует
Client
экран.
интерфейс
взаимодействует
(отношение ассоциации) с любым
классом, способным
вывод
диаграммы
осуществлять
на
экран.
Численные значения на концах связи
указывают
Примеры отношений между классами
экземпляру
на
то,
что
клиентского
каждому
класса
соответствует ровно один экземпляр
класса-отрисовщика. Обратите внимание на способ изображения комментариев. Представленные выше
механизмы позволяют получить статическую картину любой программной системы.
Рассмотрим подробнее классический пример диаграммы классов — структуру высшего учебного заведения.
Начнем описание структуры ВУЗа с главной ценности любой организации — людей. Каждый человек имеет
имя и дату рождения. Любой человек, имеющий непосредственное отношение к ВУЗу, может находиться в
нем на одном из двух положений: сотрудник или обучающийся.
Если человек является сотрудником, то он обязательно имеет конкретно место работы — какое-либо
подразделение ВУЗа (кафедра, отдел и так далее). Кроме того, каждый сотрудник работает на определенной
должности (преподаватель, бухгалтер, вахтер и тому подобное). Если работник занимается преподавательской
деятельностью, то он обязательно занимает определенную профессорско-преподавательскую должность
(ассистент, преподаватель, старший преподаватель, доцент, профессор), имеет ученое звание и ученую степень.
Схематично все вышесказанное можно представить следующим образом.
Объектно-ориентированный анализ и программирование
18
На данной диаграмме представлена
группа
классов,
отражающих
положение
преподавателя.
Абстрактный
класс
представляет
собой
любого
человека.
непосредственный
класс
People
абстракцию
Worker
Его
наследник
—
—
соответствует
работнику ВУЗа. Любой работник
может занимать одну или несколько
должностей,
иметь
определенное
место работы, а также установленное
число рабочих дней в неделю (как
правило,
пять
Абстрактный
или
шесть).
класс
Position
соответствует должности. Поле name
определяет название должности, а
поле
rate
необходимо
для
регулирования объема занимаемой
ставки.
Класс
наследник
абстракцией
RecordManager,
Position,
является
должности
«документовед» и подробно здесь не
Диаграмма классов, отражающая положение преподавателя в ВУЗе
рассматривается. Lecturer — класс,
описывающий
преподавателя.
Членами
класса,
характерными
только
для
представителей
профессорско-преподавательского состава, являются занимаемая должность (ассистент, преподаватель, доцент
и так далее), ученая степень и ученое звание. Абстрактный класс Degree имеет единственное поле типа Science
— указание на раздел науки, по которой присуждена ученая степень. Необходимо также отметить, что
предлагаемая структура допускает возможность наличия у преподавателя более одной ученой степени
(например, кандидат технических наук и доктор физико-математических).
Объектно-ориентированный анализ и программирование
Представленная
19
диаграмма
описывает структуру подразделений
ВУЗа. Абстрактный класс Workplace
соответствует
отдельному
подразделению.
Существенными
свойствами любого отдела являются
его название, адрес и тип (учебное,
административное, хозяйственное и
так
далее).
Одним
из
наиболее
важных подразделений любого ВУЗа
является кафедра. Кафедра может
быть
выпускающей
или
общеобразовательной (поле islssuer).
Будучи
отдельным
элементом
структуры ВУЗа, кафедра в то же
время обязательно входит и в состав
какого-либо
факультета.
Причем,
Диаграмма классов, соответсвующая структуре подразделений
как правило, факультет включает в
свою структуру несколько кафедр.
Данная диаграмма представляет собой абстракцию
студента как участника образовательного процесса.
Безусловно, каждый студент имеет имя и дату
People
+ name: string
+ age: integer
рождения, поэтому класс Student, как и класс
Worker, является наследником абстрактного класса
People. Студент, поступая в ВУЗ, явным образом
выбирает факультет (поле faculty), на котором он
будет
учиться,
а
также
неявным
образом
(в
зависимости от выбора направления обучения)
кафедру (поле department), сотрудники которой
будут преподавать профильные предметы. Также
студент характеризуется специализацией (то есть
той профессией, название которой будет указано в
его дипломе). Абстрактный класс FieldOfStudy как
раз и соответствует этой специализации (примером
конкретной
специализации
FieldOfStudy
является
наследник
—
ComputerScienceAndEngineering).
класс
Она
имеет
название (поле name), нормативный срок обучения
по
учебному
плану
(year).
Для
C om p liter S c ie n ce fliidE nginee ring
успешного
завершения обучения студент должен освоить ряд
Диаграмма классов, отображающая положение студента
дисциплин (массив disciplines класса FieldOfStudy).
Каждая дисциплина преподается на определенном году обучения (поле year) и требует фиксированного числа
часов для своего освоения (поле hour). Кроме того, любую дисциплину преподает сотрудник университета.
Объектно-ориентированный анализ и программирование
20
Обратите внимание на тот факт, что
преподавать дисциплину может только
сотрудник
из
бухгалтер
ВУЗа
менеджеров
будучи
числа
ППС
не
(штатный
может
бухгалтерскому
сотрудником
обучать
учету
не
какой-либо
кафедры), а одна и та же дисциплина
может быть необходима студентам разных
направлений.
Теперь
можно
изобразить
диаграмму
всего ВУЗа в целом.
ВУЗ имеет название (поле name), год
основания, которым, безусловно, гордится
(поле year), сотрудников и обучающихся
Диаграмма классов, соответствующая ВУЗу
(поле people). Кроме того, ВУЗ может
быть
представлен
как
совокупность
подразделений (поле workplaces). Важным атрибутом ВУЗа является его тип — институт, академия или
университет. Остается добавить, что представленная схема никоим образом не претендует на полноту
описания структуры высшего учебного заведения. Цель приведенного примера — показать на практике
процесс объектно-ориентированного проектирования.
Контрольные вопросы по Теме 2
1. Какие методологии проектирования легли в основу UML? В чем их особенности?
2. Как называется организация, управляющая процессом развития UML в настоящее время?
3. В чем заключается отличие между структурной и поведенческой диаграммой? Какие типы рассмотренных
диаграмм Вы бы отнесли к структурным, а какие к поведенческим?
4. Как Вы понимаете термин «пакет»?
5. Из какие блоков состоит описание класса на диаграмме классов?
6. Как Вы понимаете термин «квантор видимости»?
7. Для каких целей при описании методов используется строка-свойство?
8. Каким образом на диаграмме классов выполняется описание интерфейсов?
9. Какие отношения между классами Вам известны? В чем их особенности?
10. Какие ошибки и неточности в диаграмме, описывающей структуру высшего учебного заведения, Вы
заметили? Какие элементы, по Вашему мнению, необходимо добавить в описание структуры ВУЗа?
Выводы после Темы 2
• UML — общепринятый язык графического описания программных систем.
• UML позволяет значительно упростить этап проектирования программных систем.
• Описание системы на языке UML представляет собой совокупность специализированных структурных и
поведенческих диаграмм.
• Центральным типом диаграмм является диаграмма классов.
Объектно-ориентированный анализ и программирование
21
Тема 3. «Язык программирования Java»
На сегодняшний день рынок языков программирования предоставляет огромное количество инструментов для
разработки ПО: начиная от процедурных и заканчивая функциональными языками программирования.
Существуют также и полностью объектно-ориентированные языки, примерами которых могут служить Java и
С#. Изучение особенностей таких языков помогает разобраться в нюансах объектно-ориентированной
парадигмы.
В данной теме предлагается небольшое введение в язык программирования Java. Выбор в пользу этого языка
сделан по ряду причин, среди которых не последнее место занимает кроссплатформенность. Во время
изучения данного материла при возникновении вопросов настоятельно рекомендуется обращаться к книгам Г.
Шилдта «Полный справочник по Java» и Г. Хорстманна «Java».
История развития и характеристика языка
Для эффективного изучения любого языка программирования важно понимать обстоятельства, приведшие к
его возникновению, так как именно эти предпосылки определяют сильные и слабые стороны языка.
Возникновение новых языков программирования всегда обусловлено одной из двух глобальных причин:
изменение программного окружения вызванного эволюцией компьютерного мира и стремление к улучшению
программных средств. Появление языка Java связано и с той, и с другой причиной.
Java в некотором роде является «наследником» C++, который в свою очередь обязан своим происхождением
языку С.
В 1970-ых в мир программирования пришел к патовой ситуации. Сложность решаемых задач росла быстрее,
чем совершенствовался программный инструментарий. Существующие и популярные на то время языки
программирования (Basic, COBOL, FORTRAN) были неудобны для создания больших программ: поток
управления в них был чрезмерно запутан, частое применение оператора goto затрудняло понимание
программного кода. В то же время структурные языки программирования (такие, как Pascal) не обладали
рядом необходимых свойств для написания широкого крута программ. Сложившаяся ситуация явилась
главной причиной компьютерной революции 1970-ых — массовой разработке новых инструментов и сред
программирования. Язык С был создан в начале 70-ых годов прошлого века сотрудниками Bell Labs Деннисом
Ритчи и Кеном Томпсоном. Бытует мнение, что С — это первый язык программирования, созданный
программистами для программистов. Он успешно объединил в себе все необходимые компоненты, был прост в
изучении и позволял решать сложные задачи. Все это сделало С самым популярным языком того времени.
Следующим значимым шагом в развитии языков программирования было появление языка C++. В
определенный момент мощности С стало не хватать: структурный подход не позволял справляться с растущей
сложностью задач, требовался больший уровень абстракции. Именно в этот момент на лидирующие позиции
выходит парадигма объектно-ориентированного программирования. C++ был предложен в 1979 году
сотрудником Bell Labs Бьерном Страуструпом. Главной причиной успешного распространения C++ явилось
то, что он изначально создавался как расширение С и был полностью совместим с ним. Даже первоначальное
название языка звучало как «С with classes». Этот факт позволял разработчикам на С легко мигрировать на
C++.
Как это ни странно, но причиной возникновения Java стали растущие потребности Интернета. Первая
работающая версия языка появилась в 1995 году, хотя разработка была начата еще в 1991 году. Основным
создателем языка являлся Джеймс Гослинг.
Первоначально этот язык носил название «Оак» и был предназначен для создания программного обеспечения,
встраиваемого в бытовые устройства (стиральные машины, кофеварки, холодильники, СВЧ-печи и тому
подобное).
Дело
в
том,
что
для
управления
подобными
устройствами
используются
различные
микропроцессоры, отличающиеся набором команд. Этот факт привел к необходимости разработки языка
Объектно-ориентированный анализ и программирование
программирования, способного создавать независящие от аппаратной платформы программы. Именно эта
особенность придала языку Java одну из самых важных особенностей — кроссплатформенность. Java
относится к малой группе языков, обеспечивающих «честную» кроссплатформенность. Программа, написанная
на Java будет работать практически на любой современной популярной платформе сразу же, в то время как
программа, написанная, например, на C++ (кроссплатформенный на уровне компилятора язык) потребует
перекомплияции.
Чуть позже разработчики языка поняли, что Java идеально подходит для создания переносимых приложений, в
которых остро нуждался стремительно набирающий популярность Интернет. Функциональные характеристики
Java были скорректированы в сторону «полезности» для World Wide Web. Все это позволило выйти Java на
передний рубеж программирования.
Хотя Java тесно связан с C++, имеет похожий синтаксис и использует ряд других атрибутов C++,
существующее мнение о том, что Java является всего лишь Интернет-версией C++ в корне неверно. Несмотря
на сходство этот язык имеет принципиальные концептуальные отличия. Ошибочно также считать, что Java —
язык для разработки только для Интернета. На сегодняшний день Java позволяет с легкостью разрабатывать
программное обеспечение для смартфонов, планшетов и настольных компьютеров.
С момента выхода версии 1.0 и по сей день Java стремительно развивается. Хотя первая версия языка не
произвела революции, но уже выпущенная спустя два года (в 1997 году) версия 1.1 привлекла к себе
пристальное внимание профессионалов. Внесенные изменения (поддержка awt, RMI, JDBC, внутренних
классов, ограниченной рефлексии) были нехарактерны для изменения всего лишь второй цифры номера
версии. В конце 1998 года появилась версия языка 1.2, позже названная Java2, привнесшая еще больше
изменений, среди которых особенно стоит выделить поддержку коллекций и технологии swing. Java2 легла в
основу дальнейшего развития языка. Версии 1.0 и 1.1 с тех пор принято называть Javal. В 2000 и 2002 годах
были выпущены версии 1.3 и 1.4 соответственно. В 2004 году вышла версия 1.5, привнесшая в спецификацию
языка перечисляемые типы, поддержку аннотаций, средств обобщенного программирования и методов с
произвольным числом параметров. В 2006 и 2011 годах увидели свет версии 1.6 и 1.7. Наиболее важными
улучшениями стали расширенная поддержка сетевых протоколов, новая версия библиотеки ввода-вывода,
сжатые 64-битные указатели, улучшенное управление возможностями GPU.
Рассмотрим коротко наиболее важные особенности, характеризующие язык Java.
• Java, как уже было сказано выше, является кроссплатформенный языком.
• Java — это полностью объектно-ориентированный язык.
• В то же время Java — структурный язык.
• Java — строго типизированный язык.
• Java поддерживает парадигму императивного программирования.
• Java — безопасный язык. Программы, написанный на нем, могут быть загружены из Интернета и
выполнены в браузере (такую разновидность программ еще называют апплетами). При этом пользователь
может не бояться, что будут похищены или повреждены данные на его компьютере, так как приложение
будет запущено в специально созданной Java-среде, не имеющей доступа к файловой системе.
• Java прост и интуитивно понятен в изучении. Программист, знакомый с каким-либо языком
программирования без труда освоить Java (особенно, если этот язык С или C++).
• На Java можно создавать многопоточные приложения. Простой и изящный механизм, предложенный
разработчиками языка, позволяет программистам сконцентрироваться на конкретном поведении
многопоточной программы, а не на создании многозадачной подсистемы.
• Разработчики архитектуры Java поставили цель, чтобы приложение, написанное однажды, работало всегда,
в любое время и на любой платформе. Во многом им удалось добиться этой цели.
• Java имеет распределенный характер. Поддержка RMI (remote method invocation) позволяет выполнять
методы по сети.
22
Объектно-ориентированный анализ и программирование
23
• Java управляет памятью (используется специальный механизм сборки мусора). Это, а также то, что
запускаемый код проверяется нс только во время компиляции, но и во время запуска, позволяет
предотвратить ряд наиболее частых ошибок, приводящих к сбоям. Код Java предсказуемый, поэтому Java —
устойчивый язык программирования.
• Java — интерпретируемый язык. Программа, написанная на этом языке, компилируется в байт-код,
который представляет собой унифицированную форму записи, схожую с машинным представлением.
Байт-код интерпретируется и выполняется с помощью специализированного программного обеспечения —
виртуальной машины JVM (java virtual machine). Именно эта особенность языка гарантирует
кроссплатформснность программ. Все различия между аппаратными (архитектура центрального
процессора) и программными (особенности работы операционных систем) платформами устраняются на
этапе разработки JVM. Таким образом, программист, используя API Java, не задумывается о том, как та или
иная функция будет выполняться на другой платформе. Всю работу по реализации предоставляемого API
уже взяли на себя создатели конкретной JVM. Безусловно, интерпретация сказывается на скорости работы
java-программ, однако потери производительности на сегодняшний день в большинстве случаев нс
настолько существенны, чтобы их было необходимо принимать в расчет.
• Программы Java используют большое количество информации времени выполнения. Это позволяет
безопасно выполнять динамическое связывание кода, а значит небольшие фрагменты кода (на самом деле
— только байт-кода) могут быть обновлены в процессе выполнения программы.
Сегодня Java — это один из наиболее популярных, современных и быстро развивающихся языков
программирования, в основе которого лежит объектно-ориентированная парадигма.
Запуск программ
Для компиляции (напомним, что под термином «компиляция» подразумевается процесс генерации
байт-кода) и запуска программ, написанных на языке прог раммирования Java необходимо, чтобы в системе
присутствовал jdk (java development kit), последнюю версию которого всегда можно получить на сайте Oracle
Corporation. Вообще, JVM существует в двух вариантах — для пользователя (jre — java runtime environment) и
для разработчика (jdk). В случаях, когда требуется только интерпретация уже подготовленного байт-кода,
можно обойтись только jre, однако, но возможности, рекомендуется использовать jdk.
Процесс генерации байт-кода может быть запущен из командной строки с помощью команды «javac <имя
файла>», где в качестве параметра «имя файла» необходимо указать абсолютный или относительный путь до
заранее подготовленного файла с исходным кодом.
Рассмотрим в качестве примера такую программу.
public class Example {
public static void m a i n (String
args)
{
System, out .printin (“Привет ! Я — твой компьютер.”);
System.out.flush();
Предлагаемый код необходимо сохранить в файл Example.java. Обратите внимание — важно, чтобы файл с
исходным кодом имел расширение java и имя, совпадающее с названием главного класса. После выполнения
команды javac Example.java из содержащего файл каталога в нем же будет создан файл с байт-кодом
Example.class.
Запустить только что созданную программу можно с помощью команды «java <имя файла>». В нашем примере
выполнение команды java Example (именно так, без расширения) приведет к выводу на экран строки «Привет!
Объектно-ориентированный анализ и программирование
Я —твой компьютер.».
С аппаратной точки зрения команда «java» создаст в памяти ЭВМ отдельную копию виртуальной машины.
После этого байт-код, содержащийся в файле Example.class будет проинтерпретирован.
Обратите внимание, что в зависимости от используемой Вами операционной системы могут понадобиться
дополнительные действия. Например, команды «javac» и «java» в операционной системе Windows ХР будут
работать только после добавления пути к каталогу jdk\bin в переменную окружения Path.
Простейшая программа на языке Java
Рассмотрим подробнее представленный выше пример.
Файл Example Java содержит описание одного единственного класса с публичным модификатором доступа
(речь о модификаторах доступа пойдет ниже).
Класс Example содержит единственный метод — main. В качестве аргументов метод принимает массив
строк-параметров (String[] args). Кроме того, метод main является не только публичным, но и статическим
(статические методы также будут рассмотрены ниже).
Тело метода состоит из двух команд. Обе команды обращаются к встроенному классу System. Данный класс
позволяет использовать некоторые системные функции, содержит ряд статических методов и доступен для
всех программ по умолчанию. Первая команда осуществляет вывод строки-параметра на экран (через
обращение к потомку out, являющемуся членом класса System). Вторая команда предназначена для сброса
буфера и вывода еще не выведенного содержимого на экран. В зависимости от используемой аппаратной и
программной платформы данная команда может являться не обязательной.
Обратите внимание на название и способ объявления метода. Дело в том, что main является своеобразной
«точкой входа» для виртуальной машины. Любая java-программа должна иметь главный класс, содержащий
именно такой метод. Таким образом, работа любой программы начинается с выполнения команд,
расположенных в данном методе.
Наиболее важные особенности языка программирования Java
В данном разделе будут освещены наиболее интересные и важные с практической точки зрения особенности
языка программирования Java. Для знакомства с базовым синтаксисом языка, если это требуется,
рекомендуется обратиться к специализированной литературе.
Встроенные типы данных и механизм автоупаковки
Как и любой язык программирования Java обладает набором встроенных типов данных: целых, вещественных,
логических и символьных.
К целочисленным типам данных относятся byte (диапазон допустимых значений от -128 до 127), short
(диапазон допустимых значений от -32768 до 32767), int (диапазон допустимых значений от -2147483648 до
2147483647) и long (диапазон допустимых значений от -9223372036854775808 до 9223372036854775807).
Обратите внимание на то, что Java не позволяет работать с беззнаковыми целыми числами.
К вещественным типам данных относятся float (приблизительный диапазон допустимых значений от -1.4е-045
до 3.4е+038) и double (приблизительный диапазон допустимых значений от -4.9е-324 до 1.8е+308). Данные
типы полностью соответствуют стандартным числам с одинарной и двойной точностью, описанным в стандарте
IEEE-754. Использование double рекомендуется в тех случаях, когда требуется повышенная точность
(способен обеспечить точность до 15-16 значащих цифр; ширина 64 бита). Применение вещественных чисел
типа float позволяет сэкономить память (ширина 32 бита).
Для работы с логическими выражениями полезным может оказаться тип boolean. Переменные данного типа
способны принимать одно из двух значений: true или false.
24
Объектно-ориентированный анализ и программирование
Для работы с символьными данными используются типы данных char и Siring (формально, String —это не тип
данных, а класс; однако на практике использовать экземпляры данного класса приходится настолько часто, что
речь о нем пойдет в этом разделе). Важно помнить, что в отличие от подавляющего большинства языков
программирования тин char в Java двухбайтовый. Именно такая размерность необходима для представления
символов в Юникоде (эта кодировка используется в java-npoipaMMax по умолчанию как наиболее
универсальная). Интересным является то, что экземпляры класса String являются неизменяемыми. Это значит,
что, например, приписывание к существующей строке одного символа приведет к созданию нового объекта.
Данная особенность не может не сказываться на быстродействии. Если возникает необходимость в частом
изменении строковых объектов, возможно, лучше воспользоваться классом StringBuilder.
В версии 1.5 для стандартного средства вывода данных PrintWriter был добавлен метод prinlf, с помощью
которого можно весьма просто осуществлять форматированный вывод. В основе предлагаемого подхода лежит
механизм, использующий строку спецификации (схожая идея применяется в ряде других языков
программирования, таких как C++ и С#). Начиная с версии 1.7 для ввода-вывода предлагается использовать
средства пакета то.
Крайне полезной особенностью Java является встроенная длинная арифметика. Работа с длинными целыми
числами осуществляется с помощью класса Biginteger. С вещественными числами большой разрядности
позволяет работать класс BigDccimal. Данные классы позволяют выполнять целый ряд операций с числами:
сложение, вычитание, умножение, деление, деление с остатком, побитовый сдвиг, проверка простоты, поиск
наибольшего общего делителя и так далее.
Одной из наиболее интересных особенностей Java является механизм автоупаковки. Известно, что Java —
полностью объектно-ориентированный язык. Однако, можно заметить, что в в стандарте языка присутствуют
характерные для многих языков тины данных: byte, short, ini, long, float, double, boolean и char. Каким же
образом язык может быть с одной стороны полностью объектным, а с другой поддерживать работу с базовыми
типами? Секрет в том, что Java имеет также классы, эквивалентные каждому из этих типов: Byte, Short, Ini,
Long, Float, Double. Boolean и Char. Там, где это возможно, интерпретатор оперирует примитивами. Если же
работа с ними идет в разрез с объектно-ориентированной парадигмой, неявно выполняется преобразование
примитивов в соответствующие классы. Этот механизм получил название «автоупаковка». С его помощью
достигается баланс между производительностью и парадигмальной целостностью.
Кроме того, предлагаемые классы-заменители позволяют выполнять множество полезных операций:
преобразование числа в строку и обратно, перевод числа из одной системы счисления в другую и так далее.
Массивы и коллекции
Java позволяет взаимодействовать с именованными группами отдельных объектов. Объявление массива
выполняется согласно следующему шаблону.
<тип данных>[] <имя массива> = new <тип данных>[<размерность>];
Объявление многомерных массивов выполняется аналогичным образом. Например, трехмерный массив
описывается следующим образом.
<тип данных>[][][] <имя массива> = new <тип данных>[<размерность 1>][<размерность 2>][<размерноеть 3>];
Все массивы индексируются с нуля. Массив заранее известных величин может быть создан с помощью
следующего шаблона.
<тип данных> [] <имя массива> = {<список значений:»};
При этом значения перечисляются через запятую.
25
Объектно-ориентированный анализ и программирование
26
Важно помнить, что при создании массива классов сами элементы не будет созданы (то есть все элементы
будут тождественны null), а будет выделена намять только под массив в целом. Для инициализации придется
вызвать консгруктор для каждого элемента. Java также поддерживает работу с коллекциями элементов.
Среди наиболее интересных реализаций коллекций стоить отметить Vector (позволяет работать с массивами
переменной длины), Array List (также работает с динамическими массивами, отличается от Vector скоростью
выполнения некоторых операций), LinkedList (позволяет работать с группой элементов как со связным
списком), HashSet (создает коллекцию, которая использует для хранения элементов хэш-таблицу), TreeSet
(использует для хранения элементов древовидную структуру), PriorityQucue (создает очередь с приоритетами
на базе компаратора), ArrayDcque (позволяет работать с двунаправленной очередью).
Коллекция может включать как элементы примитивных типов (точнее, их объектных оберток), так и элементы
сложных типов. Например, вектор элементов пользовательского тина (класса) Point может быть объявлен с
помощью следующей команды.
Vector<Point> points
new
Vector<Point>();
Для перебора элементов коллекции может быть использована альтернативная запись цикла for, обычно
называемая for-each. Она имеет следующую форму.
£ог(<тип элементов> <переменная>: <коллекция>)
{ }
Указанная переменная последовательно примет значение всех элементов коллекции, состоящей из элементов
данного тина. Данная форма цикла может быть применена для любого типа коллекций (в том числе и
пользовательского), реализующего интерфейс Iterable.
Область видимост и элемент ов и пакетная организация иерархической структуры
Для более гибкой организации каталогов исходных кодов в Java применяется знакомый из предыдущих
материалов пакетный подход. Пакет, как правило, включает в себя группу классов, предназначенных для
решения одной задачи (например, обработка текста, отформатированного согласно шаблону) или схожих по
своему назначению (например, библиотека алгоритмов матричной алгебры).
Для указания того, что класс принадлежит определенному пакету, необходимо предварить описание строкой
«package <имя пакета>;». Таким образом, данная строка должна быть первой строкой файла. Кроме того, с
точки зрения операционной системы исходный файл должен лежать в соответствующем пакету каталоге. На
название пакета распространяются те же ограничения, что и названия классов, методов и неременных. Если
пакет не указан явным образом, то класс будет помещен в пакет по умолчанию (default). Обычно, в названии
пакетов используются только символы нижнего регистра. Пусть класс Figure необходимо поместить в пакет
geometry. Тогда инструкция указания пакета будет иметь такой вид.
package geometry;
Классы, расположенные в разных пакетах изначально недоступны друг для друга. Для того, чтобы
оперировать экземплярами класса из другого пакета, необходимо явным образом указать интерпретатору
месторасположение такого класса. Сделать это можно с помощью инструкции «import сполный путь к классу в
иерархии пакетов>;». Например, если класс Figure расположен в пакете geometry, который в свою очередь
расположен в пакете example, то инструкция импорта будет иметь следующий вид.
import example.geometry.Figure;
С помощью символа «*» можно импортировать все классы, содержащиеся в пакете. Так, чтобы интерпретатор
мог работать со всеми классами из пакета example, необходимо добавить такую инструкцию.
import example.*;
Объектно-ориентированный анализ и программирование
27
Обратите внимание, что при этом классы из пакета geometry не будут импортированы. В случае, когда
требуется импортировать несколько классов, можно указать несколько инструкций импорта друг за другом.
Инструкции импорта должны идти сразу же за указанием принадлежности к пакету. Таким образом, формат
файла с исходным кодом на языке программирования Java можно описать следующим шаблоном.
<инструкция принадлежности к пакету>
<инструкции импорта>
<код>
Полезным средством является статический импорт, который позволяет обращаться к статическим членам без
явного указания класса-владельца. Рассмотрим данную функцию на примере. Пусть метод getSquareO класса
Circle выглядит следующим образом.
public double getCirclef)
{
return 3.14 * Math.pow (this.radius,
2);
При вычислении площади круга происходит обращение к методу pow класса Math, который возвращает
результат возведения первого аргумента в степень, равную второму аргументу. Класс Math содержится в
пакете java.lang, поэтому не нуждается в импорте. Статический импорт позволяет обращаться к методу pow
без указания класса-владельца. Для этого необходимо использовать инструкцию import с ключевым словом
static.
import static java._ang.Math.pow;
Теперь метод gctSquare() может быть записан следующим образом.
public double getCircle()
{
return 3.14 * p o w (this.radius, 2);
Инструкция статического импорта также допускает использование символа «*» для добавления всех
статических членов.
Несмотря на все преимущества статического импорта важно не злоупотреблять им, так как любая
операция импорта (не только статического) замедляет процесс интерпретации (JVM приходится обращаться к
большему числу классов). Кроме того важно помнить о проблеме конфликтов в пространстве имен.
Добавление совершенно ненужного класса с помощью символа «*» может привести к сокрытию необходимого
в работе члена класса.
После рассмотрения реализации механизма пакетов в Java можно перейти к классификации областей
видимости.
В Java каждый член класса может иметь один из грех спецификторов видимости.
• Элемент класса, объявленный с ключевым словом «public» будет виден для всех остальных классов.
• Элемент класса, объявленный с ключевым словом «private» будет виден только внутри данного класса.
• Элемент класса, объявленный с ключевым словом «protected» будет виден внутри данного класса и внутри
всех потомков.
• Наконец, элемент класса, объявленный без какого-либо модификатора будет виден только в пределах
пакета.
Например.
class Circle {
private double radius;
Объектно-ориентированный анализ и нрофаммирование
public double getRadius()
28
{
return this. radius;
public void setRadius(double radius)
{
t h i s .radius = radius;
В данном примере поле radius является скрытым для всех других классов. Для доступа к нему необходимо
использовать публичные методы gclRadiusO и sciRadius().
Спецификаторы видимости позволяют гибко манипулировать доступом к данным и методам. Нс стоит
пренебрегать ими. Общее правило таково — если посторонним не нужно знать ничего о поле или иметь
возможность вызвать метод, то лучше скрыть такой член класса от доступа (вспомните об одном из
принципов объектно-ориентированного нрофаммирования — инкапсуляции).
Статические члены класса
Статическим членом в Java называется такой член, доступ к которому возможен без создания экземпляра
класса. Зачем нужны такие члены? В некоторых случаях бывает удобно манипулировать сущностью без
указания конкретного экземпляра. Пример подобной ситуации — необходимость создания класса-логгера,
который бы вел журнал с описанием всех изменений, произошедших в системе. Здесь целесообразно
использовать в качестве логгера класс со статическими методами для записи в журнал. Такое решение
позволить упростить получение доступа к журналу (нс потребуется ссылка на конкретный экземпляр класса).
Для того чтобы объявить статический метод, необходимо указать перед ним ключевое слово sialic. Вспомните
описание метода main класса Example. Подобным образом и реализуется точка входа в систему. Статический
характер main обеспечивает возможность выполнение данного метода в самом начале работы программы,
когда нс создано еще ни одного класса.
Для создания статического поля точно также используется ключевое слово static. Рассмотрим пример. Пусть
имеются два класса: TestWrilcr и SlaticTestWrilcr.
public class Testwriter {
public void printstring() {
System.out.printin(“This string was printed by
TestWriter.”) ;
System.out.flush() ;
public static void m a i n (String I. args)
{
StaticTestWriter.printString() ;
TestWriter tw
new TestWriter();
tw.printStrrngO ;
public class StaticTestWriter {
public static void printstring() {
System.out.printin(“This string was printed by
Объектно-ориентированный анализ и программирование
29
StaticTestWriter.”);
System.out.flush();
В представленном примере метод printString класса StaticTestWriter может быть вызван без создания
экземпляра через непосредственное обращение по имени класса. Для вызова же метода printString класса
TestWritcr требуется создание экземпляра и обращение через имя конкретной сущности.
Важно также помнить, что статические поля являются общими для всех экземпляров класса. Это означает,
что изменение значения ноля в одном месте приведет к изменению значения во всех экземплярах.
Конструкторы н деструкторы. Сборка мусора
Так как Java полностью объектно-ориентированный язык, и каждая сущность в нем является объектом, одним
из наиболее важных элементов языка являются конструкторы.
Напомним, что конструктором называется специализированный метод, выполняемый при создании объекта.
Парным по идеологии к конструктору является деструктор — специализированный блок инструкций,
выполняемый при уничтожении объекта.
Конструктор в Java выглядит как обычный метод, за тем исключением, что его имя должно совпадать с
названием класса. Класс может иметь несколько конструкторов, но в таком случае их набор параметров
должен отличаться с точки зрения компилятора (или методы должны отличаться сигнатурой). Рассмотрим
следующий пример.
public class Rectangle {
int width;
int height;
public Rectangle
() {
this. width = 0;
this. height = 0;
public Rectangle
(int size)
{
this. width = size;
this. height = size;
public Rectangle
(int width, int height)
{
this. width = width;
this. height = height;
В данном примере класс Rectangle содержит два поля — ширину и высоту — и три конструктора. Первый из
них создает прямоугольник с нулевыми длиной и шириной, второй — квадрат, а третий — фигуру с заданным
размерами. Java допускает ссылаться из одного конструктора на другой с помощью ключевого слова this
(позволяет получить ссылку на экземпляр объекта с которым идет работа, то есть ссылку «на себя»). Таким
образом, класс Rectangle может иметь также следующий вид.
Объектно-ориентированный анализ и программирование
30
public class Rectangle {
int width;
int height;
public Rectangle () {
this(0, 0);
public Rectangle (int size) {
this(size, size);
}
public Rectangle (int width, int height)
this.width = width;
{
this.height = height;
}
Здесь первый и второй конструктор ссылаются на третий. Пользователь же, в соответствии с принципом
инкапсуляции, нс знает о том, как организована работа конструкторов. Для него существует три различных
способа создания объекта класса Rectangle, хотя на самом деле сущность создается единственным образом.
Для ограничения доступа к конструкторам, точно также как и для других методов, могут применяться
модификаторы доступа protected и private.
Если класс не содержит описания ни одного конструктора, то при создании экземпляров такого класса
будет вызван конструктор но умолчанию (так как все классы являются наследниками Object). Этот
конструктор не позволит принять ни одного параметра, а единственным совершаемым в нем неявно действием
будет выделение памяти под новую сущность.
Как было сказано выше, деструктор, в противовес конструктору, применяется для уничтожения экземпляра
класса. Java, в отличие от большинства языков про1раммирования, не позволяет создавать деструкторы. Такое
ограничение связано с тем, что в Java применяется механизм сборки мусора (garbage collection).
В чем же заключается суть этого механизма? Дело в том, что виртуальная машина сама контролирует
состояния всех объектов. Если работа с объектом завершена и на него не осталось ссылок, то сборщик мусора
сам освободит всю намять, занятую объектом. Программисту не нужно заботиться о корректном уничтожении
сущностей.
Сборка мусора позволяет предотвратить огромное количество ошибок, связанных с неправильной работой с
памятью. Обратной стороной является невозможность совершения завершающих действий (например,
освобождение дескриптора файла) после того, как объект выполнил свою работу и может быть уничтожен.
Единственным способом выполнить какие-либо действия при уничтожении объекта является использование
метода finalize. Его каноническая форма выглядит следующим образом.
protected void finalized
{
//...
Модификатор protected необходим для защиты от вызова метода из постороннего участка кода.
Инструкции, включенные в данный метод, будут выполнены при уничтожении объекта сборщиком мусора.
Однако когда произойдет сборка предсказать невозможно. К сожалению, в силу особенностей реализации не
Объектно-ориентированный анализ и программирование
31
существует возможности инициировать работу сборщика в произвольный момент, самостоятельно может быть
изменена только стратегия его поведения, но и это не даст каких-либо конкретных сведений о сроках
уничтожения объектов. Кроме того, важно помнить, что частая сборка является одной из возможных
проблем с производительностью.
Таким образом, единственно правильным стилем программирования на Java является отказ от совершения
действий при уничтожении объектов в тех случаях, когда это возможно (практически всегда). Хорошо
написанная программа не должна зависеть от метода finalize)), а все используемые ресурсы, должны
освобождаться с помощью штатных средств языка.
Наследование. Обращение к суперклассу
Н Java любой класс может являться наследником другого класса. При этом каждый класс может
наследовать только от одного класса. Таким образом в Java решается характерная для многих языков
программирования проблема множественного наследования.
Для того чтобы объявить класс-наследника, необходимо использовать ключевое слово extends. Если
родительский класс не задан явным образом, то объект считается наследником класса Object. Рассмотрим
следующий пример.
public class Parent {
public int a;
public int b;
public Parent(int a, int b) {
this.a = a;
this.b = b;
public void print(int a, int b)
{
i f (a > b)
System.out.printin (“First argument is greater than
second”) ;
else
if(a < b)
System.out.printin (“Second argument is greater
than first”) ;
else
System, out .printin (“Arguments are equal”);
System.out.flush();
public class Child extends Parent {
public Children(int a) {
super(a , 0);
public void print(int x) {
Объектно-ориентированный анализ и программирование
32
i f (а + b >= х)
System, out .printin (“Sum of a and b is greater or
equal than argument”) ;
else
Sys t e m .o u t .printi.n (“Sum of a and b is less than
argument”) ;
System.o u t .flush ();
public static void m a i n (String[] args)
Child ch
{
new Child(l);
ch.p r i n t (2);
c h .p r i n t (1);
c h .p r i n t (100, 1);
ch.p r i n t (1, 100);
ch.print (100,
100);
В приведенном примере объявляются два класса, первый из которых содержит два поля и метод для сравнения
пары аргументов. Второй класс расширяет первый и содержит метод, позволяющий оценить соотношение
значения суммы полей класса-родителя и заданного аргумента.
Особое внимание стоит обратить на использование ключевого слова super, которое в данном контексте
используется для вызова родительского конструктора. С его помощью может быть также выполнено
обращение к переопределенному методу. Однако, пожалуй, самым важным назначением ключевого слова
super является возможность обращения к суперклассу. Такая форма действует подобно ключевому слову this
за тем исключением, что ссылка осуществляется на родителя. Обращение к членам суперкласса может быть
выполнено с помощью следующей конструкции.
super.<член>;
Данная форма полезна в тех случая, когда из-за конфликта пространства имен могут быть скрыты некоторые
элементы класса-родителя.
Пара методов print() иллюстрируют принцип полиморфизма в действии. В случае, когда метод вызывается с
единственным аргументом, выполняется сравнение суммы полей и переданного параметра. Если же при
вызове метода будет передана пара аргументов, то будет вызван унаследованный от родителя вариант метода,
сравнивающий заданные параметры.
Хотя рассмотренный пример и не имеет большой практической ценности, он, тем не менее, позволяет
получить представление о механизме наследования в Java.
Объектно-ориентированный анализ и программирование
33
Модификатор final
Модификатор final может быть применен к нолям, методам, классам и позволяет решить три задачи.
Если модификатор final применятся к нолю, то его значение не может быть изменено в процессе работы.
Таким образом, с помощью данного модификатора могут быть созданы константные значения.
public class Circle {
private final double pi = 3.14;
public double radius;
public Circle(double radius)
{
this. radius = radius;
public double getSquareO
{
return pi * radius * radius;
}
В данном примере поле pi жестко проассоциировано со значением 3,14. Подобное связывание положительно
сказывается на скорости работы java-машины. Вторым назначением модификатора final является создание
нспсрсопрсделясмых методов. То есть метод, объявленный как final, не может быть переопределен в
классе-потомке.
public class Parent {
public final void print() {
System.out.printin(“This method has modifier final”);
System.out.flush();
public class Child extends Parent {
public void print() {
System, out .printin (“Error ! ! !”) ;
System.out.flush();
Представленный пример нс сможет быть скомпилирован, так как класс-наследник пытается переопределить
метод с модификатором final.
Третьим назначением fianl является объявление классов, которые не могут иметь потомков.
public final class Parent {
public class Child extends Parent {
Представленный пример также приведет к ошибке времени компиляции, так как класс, имеющий
модификатор final, не может быть расширен.
Объектно-ориентированный анализ и программирование
34
Как следует из приведенных примеров, модификатор final позволяет создавать сущности или элементы
сущностей, являющиеся конечными в иерархической цепочки наследования. Подобная информация является
крайне ценной для компилятора и позволяет генерировать более эффективный байт-код. Нс нужно
пренебрегать данным модификатором, если известно, что переопределение или изменение сущности или ее
элемента в дальнейшем нс потребуется.
Абстракт ные классы и интерфейсы
Идея абстрактных классов является одной из основных в объектно-ориентированном про1раммировании.
Подобные сущности, как правило, представляют собой наиболее глубокие абстракции, обладающие
наибольшим
объемом
при
наименьшем
содержании.
Если
не
все,
то
практически
все
объектно-ориентированные языки позволяют взаимодействовать с абстрактными сущностями, однако везде в
этот термин вносятся некоторые особенности.
В Java абстракт ным называется такой класс, который не может иметь экземпляров. Свойственное для многих
языков ограничение, обязывающее абстрактный класс иметь только абстрактные методы, в Java не
накладывается.
Для создания абстрактного класса необходимо использовать при объявлении модификатор abstract.
Абстрактные класс может содержать поля и методы. Если какой-либо метод такого класса является
абстрактным, то он обязательно должен быть переопределен в классе-потомке. Пример абстрактного класса и
его потомка представлен ниже.
public abstract class Parent {
protected int state;
public void p r i n t () {
System.out.printin("This string was printed from class
Parent");
System.o u t .f l u s h ();
public abstract boolean isAbstract();
public class Child extends Parent
public C h i l d (int state)
{
{
this.state = state;
public void p r i n t () {
System.out.printin("This string was printed from class
Child");
System.out.flush ();
public boolean isAbstract() {
System.out.printin("This class is not abstract");
System.out.printin("His state is equal " + state);
Объектно-ориентированный анализ и программирование
35
S y s te m .o u t . f l u s h () ;
r e tu r n f a l s e ;
В представленном примере класс Parent имеет одно поле, видимое для всех классов-потомков, и два метода.
Первый метод выводит сообщение, ин(|х>рмирующее о том, что обращение выполнялось к экземпляру класса
Parent. Второй метод является абстрактным и должен быть переопределен в классах-потомках. Класс Child
наследует от класса Parent. Публичный конструктор Childtint state) инициализирует унаследованное поле.
Потомок реализует абстрактный метод isAbstract и переопределяет необходимым образом метод print.
Одной из важных идей объектно-ориентированного
программирования
является
множественное
наследование
ситуация
при
—
которой
класс-потомок одновременно является наследником
нескольких родительских классов. Практически все
языки программирования так или иначе реализуют
эту идею: C++. Eiffel, Python, Javascript, Perl, Lisp.
Delphi и так далее. Однако разработчики Java
предпочли отказаться от поддержки множественного
наследования. Дело в том, что при определенной
иерархической структуре приложения наследование
от нескольких классов чревато конфликтами. Такая
ситуация
получила
название
ромбического
наследования. Вкратце она может быть описана
следующим
образом.
Пусть
имеется
класс
Ромбическое наследование
А,
содержащим метод method(). Пусть класс А имеет двух наследников В и С, каждый из которых переопределяет
methodt). Класс D наследует от классов В и С одновременно. Конфликт возникает в том случае, когда через
экземпляр класса D происходит вызов метода methodt), так как компилятор не знает к какой конкретно
реализации methodt) обращаться. Отметим также, что распространенным является мнение, согласно которому
необходимость множественного наследования всегда связана с ошибками проектирования и анализа.
Взамен механизма множественного наследования Java предлагает гибкий и мощный механизм интерфейсов.
Напомним, что под интерфейсом в объектно-ориентированном программировании принято понимать
семантическую и синтаксическую конструкцию в коде программы, используемую для специфицирования
услуг, предоставляемых классом. Объявление интерфейса в Java схоже с объявлением класса. Любой
интерфейс может содержать произвольное число полей и методов. При этом все поля автоматически получают
модификаторы public final static, а методы — public abstract. Так как поля среди прочих имеют модификатор
final, то потребуется их сразу же проинициализировать.
Класс, реализующий интерфейс, получает доступ ко всем его полям и должен содержать описание всех
методов. Для связывания класса и интерфейса используется ключевое слово implements. Разрешается
реализовывать несколько интерфейсов. Значительная гибкость механизма интерфейсов в Java достигается за
счет множественного интерфейсного наследования, то есть интерфейс может являться наследником
произвольного числа других интерфейсов. Для указания родительских элементов применяется следующая
форма.
i n t e r f a c e <имя и н т е р ф е й с а e x te n d s < р о д и тел ьск и е интерфейсы>
Объектно-ориентированный анализ и нрофаммирование
36
Здесь интерфейс является наследником родительских интерфейсов (допускается перечисление нескольких
родителей через запятую). Важно помнить, что при множественном интерфейсном наследовании не должно
образовываться циклических зависимостей. Кроме того, наличие среди членов родительских элементов
методов с одинаковой сигнатурой, но разными возвращаемыми значениями также приведет к ошибке (в
случае полного совпадения сигнатур потребуется всего лишь одна реализация такого метода).
Рассмотрим пример.
p u b l i c a b s t r a c t c l a s s P e o p le
{
p r o t e c t e d int age;
p r o t e c t e d String name;
p u b l i c People(String name)
{
t h i s . name = name;
p u b l i c a b s t r a c t String getStatusO;
p u b l i c c l a s s S t u d e n t e x t e n d s People {
p r i v a t e String university;
p u b l i c Student(String name, String university)
{
s u p e r ( name);
t h i s . university = university;
p u b l i c String getStatusO
re tu rn
{
"I'am a student of " + t h i s . u n i v e r s i t y ;
p u b l i c i n t e r f a c e W o rk in g W ith D a te {
int getAge();
void setAge(int age);
p u b l i c i n t e r f a c e P e r s o n a l C a r d e x t e n d s WorkingWithDate, YearsOfStuding {
String getUniversity();
p u b l i c c l a s s S tu d e n t I D im p le m e n ts PersonalCard {
p r i v a t e Student owner;
p u b l i c StudentID(Student owner)
t h i s . o w n e r = owner;
p u b l i c int getAge() {
{
Объектно-ориентированный анализ и программирование
return owner.age;
public void setAge(int age)
{
owner.age = age;
public String getUniversity() {
return owner.getStatus();
В данном примере класс StudentID представляет собой абстракцию индивидуальной электронной студенческой
карты, внедряемой в некоторых университетах (упрощенный вариант студенческого билета). Класс реализует
интерфейс PersonalCard, который содержит метод для получения строки с названием места обучения студента
и, будучи наследником интерфейсов WorkingWithDale и YearsOfStuding, включает в себя все методы и поля
родительских элементов. Одним из членов StudentID является ссылка на экземпляр класса Student —
владельца студенческого билета. Класс Student является наследником абстрактного класса People и, помимо
общечеловеческих свойств (таких как имя и возраст), содержит поле с информацией <ж
> университете, в
котором обучается студент. Данные владельца карты используются при реализации методов интер(|х:йса
PersonalCard. Таким образом, представленный пример содержит две вертикальные иерархии (классов и
интерфейсов), объединение которых происходит в классе StudentID.
Совместное использование абстрактных классов и интерфейсов помогает в создании простых по своей
внутренней структуре иерархий.
Сериализация
Сериализацией называется процесс перевода какой-либо структуры данных в последовательность битов.
Данная операция значительно упрощает решение задачи сохранения экземпляра класса. Сериализованный
объект может быть сохранен на жесткий диск или, например, передан по сети.
Другими словами, сериализация позволяет работать с объектом как с потоком данных.
Обратный к сериализации процесс (получение экземпляра класса из потока битов) получил название
десериализации.
Java позволяет сериализовывать и десериализовывать любые объекты двумя способами. Первый заключается
во взаимодействии с интерфейсом Serializable (стандартная сериализация), второй — с интерфейсом
Extemalizable (расширенная сериализация). Остановимся подробнее на первом способе.
Для того чтобы объект мог быть превращен в поток битов необходимо, чтобы класс, от которого порожден
объект, и все его подклассы реализовывали интерфейс Serializable. Данный интерфейс не содержит никаких
методов и является своеобразным маркером для виртуальной машины, подсказывающим ей, что экземпляры
класса могут быть сериализованы.
Модификатор transient позволяет отмечать поля, сохранение которых не требуется. Также не сохраняются
ноля с модификатором static.
Для непосредственного преобразования объект в поток данных необходимо использовать специализированные
средства ввода-вывода. Поясним вышеизложенное на примере.
public class TreeNode implements Serializable {
//. . .
37
Объектно-ориентированный анализ и программирование
38
p u b l i c c l a s s L T L T ree im p le m e n ts Serializable {
p u b l i c int rootID;
p u b l i c TreeSet<TreeNode> node;
p u b l i c String formula;
}
p u b lic c la s s T re e S e ria liz a to r
p u b lic s t a t i c
{
void main(String . args) {
String fileName = "ltltree.txt";
try
{
ObjectOutputStream oos
new ObjectOutputStream(new
FiieOutputStream(fileName));
oos.writeObject(currentTree);
c a t c h (FileNotFoundException err) {
System.o u t . p r i n t i n ("Some bad thing with file");
err.printStackTrace();
c a t c h (lOException err) {
System.o u t . p r i n t i n ( "Exception while writing");
err.printStackTrace();
LTLTree tempTree
try
n u ll;
{
ObjectlnputStream ois
new ObjectlnputStream(new
FilelnputStream(fileName));
tempTree = (LTLTree)ois.readobject();
c a t c h (FileNotFoundException err) {
System.out.printin("Some bad thing with file");
err.printStackTrace();
c a t c h (lOException err) {
System.out.printin("Exception while writing");
err.printStackTrace();
(ClassNotFoundException err) {
c a tc h
System.out . p r i n t i n ("Some bad with class-file
LTLTree");
err.printStackTrace();
Сериализация представляет собой чрезвычайно гибкий механизм, позволяющий выполнять сохранение и
последующее восстановление экземпляров сущности. Без сериализации выполнение таких рутинных операций
как передача состояния объектов но сети, вызов RMI и так далее требовало бы гораздо больших затрат
времени программиста.
Объектно-ориентированный анализ и программирование
39
Контрольные вопросы по Теме 3
1. В каких случаях целесообразно использовать статический импорт?
2. В чем необходимость использования спецификаторов видимости?
3. В чем заключается проблема множественного наследования?
4. Что такое абстрактный класс?
5. Может ли абстрактный класс иметь поле с модификатором final? Объясните свой ответ.
6. Поддерживает ли Java множественное наследование? Объясните свой ответ.
7. Какие ошибки были допущены при проектировании абстракции студенческого билета?
8. Какие два способа сериализации и десериализации объектов вы знаете? Как работает каждый из этих
способов? Могут ли они «смешиваться» (сериализация с помощью одного способа, десериализация — с
помощью другого).
9. Возможно ли изменять значения полей с модификатором final? Объясните свой ответ.
10. Для решения каких задач используются ключевые слова this и super?
Выводы после Темы 3
• Java — мощный и современный объектно-ориентированный язык программирования.
• Программы, написанные на этом языке программирования, обладают рядом полезных свойств, среди
которых особенно стоит отметить кроссплатформенность и безопасность.
• Необходимо помнить, что данный язык не всегда подходит для решений, к которым предъявляется
требование высокого быстродействия.
Тема 4. «Шаблоны проектирования»
Замечательным
примером
выразительной
мощности
объектно-ориентированной
парадигмы
программирования являются шаблоны проектирования. Прежде всего они помогают продемонстрировать
насколько просто и элегантно могут быть решены сложные архитектурные задачи. В данной теме будут
рассмотрены несколько таких решений.
Шаблон как повторяющееся решение
Шаблон проектирования — повторимая архитектурная конструкция, представляющая собой решение часто
возникающей проблемы проектирования; это описание взаимодействия объектов и классов, адаптированных
для решения задачи в конкретном контексте.
Именно шаблон и представляет собой тот универсальный «рецепт» объединения классов, позволяющий
добиваться от объектов сложного поведения с минимальными затратами на проектирование.
Шаблоны принято подразделять на низкоуровневые и высокоуровневые. Низкоуровневые шаблоны также
называются идиомами. Они учитывают специфику языка программирования и являются хорошими
решениями проектирования, характерными для конкретного языка или платформы. Низкоуровневые шаблоны
не являются универсальными. Высокоуровневые шаблоны также называются архитектурными шаблонами. Они
охватывают архитектуру всей программной системы и являются универсальными решениями.
Основные преимущества шаблонов проектирования заключаются в следующем. Во-первых, каждый шаблон
проектирования описывает решение целого ряда абстрактных проблем. Во-вторых, использование шаблонов
проектирования помогает унифицировать процесс разработки. Программисты могут вести диалог о системе,
ссылаясь на известные шаблоны. В-третьих, шаблон проектирования — это универсальное решение,
используемое множество раз. Правильно сформулированный шаблон проектирования позволяет, отыскав
удачное решение, пользоваться им снова и снова.
Объектно-ориентированный анализ и программирование
40
При всех вышеперечисленных достоинствах не стоит забывать и о недостатках, присущих шаблонам
проектирования.
В некоторых
случаях
их
использование
приводит
к
построению
громоздкой
и
малоэффективной архитектуры. Активное использование шаблонов также приводит и к резкому снижению
доли творческой работы, повышению доли механической работы. Бездумное или чрезмерное использование
«готовых рецептов» прививает плохой стиль разработки. Часто с помощью шаблонов пытаются скрыть
недостатки системы (например, отсутствующую документацию).
Классическими принято считать четыре группы шаблонов: основные, порождающие, структурные и
поведенческие.
Основные шаблоны предназначены для решения таких повседневных задач как переадресация работы
специализированному классу, обеспечение слабой связности системы и тому подобное. Подавляющее
большинство программистов неявно применяет те или иные основные шаблоны: «делегирование», шаблон
функционального дизайна, «неизменяемый объект» и так далее.
Порождающие шаблоны проектирования — это шаблоны проектирования, которые абстрагируют процесс
создания экземпляра класса. Применение шаблонов этого типа позволяет получить гребуемос сложное
поведение объекта не посредством создания новых объектов через наследование, а с помощью композиции
ряда более простых объектов. При этом порождающие шаблоны инкапсулируют знания о простых классах,
применяемых в системе. Системе известны лишь интерфейсы сложного класса, полученного в результате
композиции, но не известен принцип стыковки простых классов. К порождающим шаблонам принято относить
«абстрактную
фабрику»,
«строителя»,
«фабричный
метод»,
«ленивую
инициализацию»,
«прототип»,
«объектный пул», шаблон «одиночка».
Задача, решаемая структурными шаблонами проектирования, — создание различных структур, которые
изменяют интерфейс или реализацию уже существующих объектов. Как правило, данные действия бывают
необходимы для того, чтобы облегчить процесс разработки или оптимизировать программу. Наиболее
известными структурными шаблонами проектирования являются «адаптер», «мост», «компоновщик», «фасад»,
«декоратор», «приспособленец» и «заместитель».
Поведенческими шаблонами проектирования принято называть шаблоны, которые определяют алгоритмы и
способы взаимодействия объектов между собой. Наиболее типичными примерами поведенческих шаблонов
являются «цепочка ответственности», «команда», «интерпретатор», «итератор», «посредник», «хранитель»,
«наблюдатель», «стратегия», «шаблонный метод» и «посетитель».
Основные шаблоны проектирования
Шаблон делегирования
Одним из наиболее примитивных шаблонов проектирования является «делегирование». Решаемая им задача
заключается в передаче ответственности за выполнение работы другому объекту. Такое решение позволяет
изменить поведение без создания нового класса через наследование. Косвенным преимуществом с точки
зрения архитектуры системы является повышение степени абстракции. Среди недостатков можно отметить
снижение производительности, вызванное необходимостью дополнительной передачи параметров.
Примером использования «делегирования» является следующий код.
p u b lic c la s s B u b b le s o rte r {
p u b l i c void sort(int_. args)
boolean flag
{
tru e ;
w h i l e (flag)
{
flag = f a l s e ;
for(int 1 = 0 ;
i < args.length - 1; i++)
Объектно-ориентированный анализ и программирование
41
i f (args.i. > args.i + 1])
flag
{
true;
int tmp = a r g s . i l ;
args .i . = a r g s .i 1] = tmp;
args .i
p u b l i c class D atastorage
{
p r ivate BubbleSorter bs
n e w B u b b l e S o r t e r ();
p r iv ate int .. data;
p u b l i c D a t a s t o r a g e (int[ I data)
i f (data
{
!= null)
t h i s . data = d a t a .c l o n e ();
p u b l i c int [] g e t D a t a O
return data
p u b l i c void sort()
i f (data
{
null? null
: d a t a .c l o n e ();
{
!= null)
b s .s o r t (data);
p u b l i c static void main (String I.. args)
int..
data = {2,
Datastorage ds
3,
5,
8,
{
1, 15};
new Datastorage(data);
d s .s o r t ();
data = d s .g e t D a t a ();
i f (data
!= null)
{
f o r (int i = 0; i < data._ength;
i++)
S y stem.o u t . p r i n t ( d a t a .i . + " ");
S y s t e m . o u t .flush () ;
В представленном примере класс Datastorage предназначен для хранения массива целых чисел. Одной из
функций, которую обеспечивает Datastorage является также сортировка хранимого массива. Класс
BubbleSorter в контексте решаемой задачи является специализированным средством для сортировки набора
элементов. В связи с этим экземпляр этого класса включен в состав Datastorage как скрытый член. При вызове
метода sort() Datastorage перепоручает работу объекту класса BubbleSorter.
Объектно-ориентированный анализ и программирование
42
Неизменяемый объек т
Интересным по своей внутренней организации является «неизменяемый объект» — основной шаблон,
предназначенный для создания объектов, которые не могут быть модифицированы после своего создания.
Примером использования этого шаблона в Java является класс String.
Для создания неизменяемых объектов необходимо придерживаться следующих правил.
• Все поля класса имеют модификатор final.
• Класс объявляется как final.
• Ссылка this не должна быть передана во время конструирования.
• Для всех полей, содержащих ссылки на изменяемые объекты (например массивы), совокупности или
изменяемые классы действуют следующие правила.
• Имеют модификатор private.
• Никогда не возвращаются и никаким другим образом не становятся доступными вызывающим
операторам.
• Являются единственными ссылками на те объекты, на которые они ссылаются.
• Не изменяют после конструирования состояние объектов, на которые они ссылаются.
Очевидными преимуществами использования шаблона являются простота взаимодействия с неизменяемыми
объектами, автоматическая поддержка безопасности потоков, защита от характерных для изменяемых
объектов ошибок, а также возможность использования подобных классов в качестве ключей для
хэш-коллекций (неизменность состояния гарантирует неизменность значения хэш-функции).
Рассмотрим пример неизменяемого класса.
public final class People implements Cloneable {
private final int age;
private final String name;
private People, parent;
public People(int age, String name) {
this(age, name, null, null);
public People(int age, String name, People parent)
{
this(age, name, null, null);
public People(int age, String name, People parentO, People
parentl)
{
this.age = age;
this.name = name;
parent
new People 2.;
try {
i f (parentO != null)
parent .0. = (People) parentO.c_one();
i f (parentl 1= null)
parent, 1] = (People) parentl.c-one();
catch (CloneNotSupportedException e) {
e .printStackTrace() ;
Объектно-ориентированный анализ и программирование
43
public void printinformation() {
printName();
System.out .printin ("--------------- \nAge: " + age) ;
for(int i = 0; i < parent._ength; i++)
i f (parent.!
!= null)
{
System.out.print("Parent " + (i + 1) + ": ");
parent.i . .printName();
}
System.out.flush();
public void printName() {
System.out.pr
n(name);
System.out.flush();
public static void m a i n (Stringi. args)
{
People pl
new People(30, "Mike");
People p2
new People(28,
People p3
"Mary");
new People(6, "Andrew", pl, p2);
p 3 .printinformation() ;
В представленном примере класс People является неизменяемым. Поля age и name имеют модификатор final.
Класс реализует интерфейс Clonable, а это значит, что его экземпляры могут быть клонированы (в памяти
ЭВМ будет создана полностью независимая копия объекта). Массив parent имеет модификатор private,
передаваемые в конструктор объекты клонируются перед помещением в массив parent. Таким образом
обеспечивается безопасность ссылок. Метод printlnformationO выводит на экран полную информацию сх>
объекте, а метод printNameO — только значение поля name.
Шаблон функционального дизайна
Наиболее общим из всех является основной шаблон функционального дизайна, подразумевающий
максимально низкую связь между модулями. Каждый модуль имеет только одну обязанность и исполняет ее с
минимальным влиянием на другие части программы. Следование принципам функционального дизайна
упрощает код модулей и позволяет безопасно повторно использовать код. Простота кода модулей
позволяет добиться простоты архитектуры и упрощение процедуры внесения изменений в дальнейшем. К
сожалению, далеко нс всегда возможно выполнить полное разбиение системы таким образом, чтобы каждый
модуль отвечал только за одну функцию системы.
Объектно-ориентированный анализ и нрофаммирование
Порождающие шаблоны проектирования
Фабричный метод
«Фабричный метод» — порождающий шаблон проектирования, предоставляющий подклассам интерфейс
для создания экземпляров некоторого класса. Фабрика делегирует создание объектов наследникам
родительского класса. Такой прием позволяет использовать в коде программы нс специфические классы, а
манипулировать абстрактными объектами на более высоком уровне.
Шаблон имеет смысл использовать в одном из трех случаев.
• Если классу заранее неизвестно, объекты каких подклассов ему нужно создавать.
• Если класс спроектирован так, чтобы объекты, которые он создает, специфицировались подклассами.
• Когда класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, а уточнение
информации о том, какой класс примет эти обязанности на себя, планируется в процессе выполнения.
Применение данного подхода позволяет сделать код создания объекта более универсальным: нс привязывать
его к конкретным классам, а оперировать интерфейсом. Вместе с тем наиболее существенным недостатком
предложенного шаблона является необходимость в наследнике класса-создателя для каждого нового типа
класса-продукта.
Рассмотрим такой пример.
p u b lic in t e r f a c e S o rte rG e n e ra to r {
Sorter getSorter();
p u b lic in te r f a c e S o rte r
{
void sort(int_. args);
p u b l i c c l a s s A s c e n d i n g B u b b l e S o r t e r G e n e r a t o r im p le m e n ts SorterGenerator
{
p u b l i c Sorter getSorter()
{
r e t u r n new AscendingBubbleSorter();
p u b l i c c l a s s D e s c e n d i n g B u b b l e S o r t e r G e n e r a t o r im p le m e n ts SorterGenerator
{
p u b l i c Sorter getSorter()
{
r e t u r n new DescendingBubbleSorter();
p u b l i c c l a s s A s c e n d i n g B u b b l e S o r t e r im p le m e n ts Sorter {
p u b l i c void sort(int_. args)
boolean flag = t r u e ;
w h i l e (flag)
{
{
44
Объектно-ориентированный анализ и программирование
45
flag = f a l s e ;
f o r (int i = 0; i < args._ength - 1; i++)
i f (args.i) > args.i + 1])
{
flag = t r u e ;
int tmp = a rgs.i .;
args[ij = args[i + 1] ;
args.i + 1] = tmp;
}
}
}
}
p u b l i c c l a s s D e s c e n d i n g B u b b l e S o r t e r i m p l e m e n t s Sorter
p u b l i c void sort(int[J args)
{
boolean flag = t r u e ;
w h i l e (flag)
{
fa ls e ;
flag
f o r (int i = 0; i < args . _ e n g t h
i f (args[i] < argsfi + 1])
-
1; i++)
{
flag = t r u e ;
int tmp = args .i .;
args.i . = args.i + 1];
args.i + 1] = tmp;
}
}
p u b lic c la s s C lie n t
p riv a te
{
s ta tic
void print(int.. args)
i f (args != n u l l )
{
{
f o r (int i = 0; i < args.length; i++)
System.out.print(args_i. + " ");
System.out.printin ();
System.out.flush();
}
p u b lic
s ta tic
void m a i n (String . args)
{
intCl data = {3, 2, 1, 5, 4};
SorterGenerator sg
new AscendingBubbleSorterGenerator();
Sorter s = s g .getSorter() ;
s .sort(data);
print(data);
sg
n ew DescendingBubbleSorterGenerator();
Объектно-ориентированный анализ и программирование
46
s = sg.getSorter();
s .sort(data);
print(data);
В данном примере классы AscendingBubbleSortcrGcnerator и DescendingBubblcSorterGenerator реализуют
интерфейс SorterGenerator, включающий в себя единственный метод getSorter(), который в свою очередь
возвращает экземпляр
класса,
реализующего
интерфейс Sorter.
Примерами
таких
классов
служат
AscendingBubbleSorter и DcscendingBubbleSorter: первый из них позволяет выполняет сортировку массива
целых чисел по возрастанию, второй — но убыванию. Класс Client, используя возможности обоих,
упорядочивает набор чисел, выводя результат на экран.
Строитель
Похожим на «фабричный метод» является «строитель» — порождающий шаблон проектирования, который
отделяет конструирование сложного объекта от его представления. Таким образом, в результате одного и того
же процесса конструирования могут получиться разные представления. По сути, строитель — это
«половинка фабричного метода», отвечающая за создание классов-продуктов.
Среди основных преимуществ данного шаблона особенно стоит отмстить следующие.
• Возможность изменить внутреннее представление продукта. Так как продукт конструируется через
абстрактный интерфейс, то для изменения внутреннего представления продукта достаточно всего лишь
определить новый вид строителя.
• Изоляция кода, реализующего конструирование и представление, от клиентов, которым нет необходимости
знать что-то о внутренней структуре продукта (принцип инкапсуляции).
• Более тонкий контроль надо процессом конструирования. В отличие от других порождающих шаблонов,
здесь процесс конструирования осуществляется шаг за шагом, что обеспечивает лучший контроль над
процессом и внутренней структурой продукта.
Рассмотрим пример.
import jav a .u t i l .Random
public class Point {
private int x, y;
public Point() {
this.x = 0;
this. у = 0;
public void setX(int x)
{
this.x = x;
}
public void setY(int y)
this. у = у;
public void print() {
{
Объектно-ориентированный анализ и нрофаммирование
47
System.out.printin("X = " + х + "; Y = " + у);
System.out.flush();
public interface Builder {
void buildPart(Point pnt);
}
public class BuilderX implements Builder {
public void buildPart(Point pnt)
Random rnd
{
new Random();
p n t .setX(rnd.nextInt(100) + 1) ;
}
}
public class BuilderY implements Builder {
public void buildPart(Point pnt)
Random rnd
{
new Random();
p n t .setY(rnd.next!nt(100) + 1) ;
}
}
public class Client {
private Builder bl, b2;
public Client(Builder bl, Builder b2)
{
this.bl = bl;
this.b2 = b2;
}
public Point construct() {
Point pnt
new Point();
b l .buildPart(pnt);
b 2 .buildPart(pnt);
return pnt;
}
public static void m a i n(String . args)
Client cl
{
new Client(new BuilderX(), new BuilderY());
f o r (int i = 0; i < 5; i++)
c l .construct().print();
Объектно-ориентированный анализ и профаммирование
48
В предлагаемом примере классы BuilderX и BuilderY реализуют интерфейс Builder, декларирующий метод
buildPart. Класс Client посредством метода construct)) способен создавать экземпляр Point. При создании
объекта подклассы типа Builder определяют координаты создаваемой точки. Текущий набор строителей
генерирует точку, расположенную в первой четверти координатной плоскости. Для изменения места
расположения генерируемой точки достаточно заменить один подкласс (или оба) Builder класса Client. Метод
print() класса Point выводит на экран информацию о созданной сущности.
Основным преимуществом над «фабричным методом» является то, что «ст роитель» придает процессу создания
класса большую гибкость.
Отложенная инициализация
Чрезвычайно полезным на практике является шаблон отложенной инициализации. В его основе лежит
предложенный Беллманом при развитии парадигмы динамического программирования принцип отложенной
операции — составной части ленивого программирования. Идея этого принципа заключается в том, что
некоторая ресурсоемкая задача выполняется только в тот момент, когда потребуется ее результат.
Частный
случай
отложенной
операции
—
порождающий
шаблон
проектирования
«отложенная
инициализация» — создание объекта в момент первого обращения к нему. При использовании данного
шаблона инициализация объекта выполняется только в тех случаях, когда она действительно необходима. Так
как инстанцирование объектов чаще всего выполняется не в момент старта приложения, то таким образом
достигается ускорение начальной инициализации. Однако, при обращении к объекту в первый раз возникает
задержка, обусловленная процессом созданием объекта. Пожалуй, основным недостатком является то, что
шаблон не позволяет явным образом задать порядок инициализации.
Вернемся к примеру с классами Datastorage и BubbleSorter. Очевидно, что сортировка хранимых в Datastorage
данных может потребоваться не во всех случаях, поэтому правильнее будет создавать экземпляр объекта
BubbleSorter только в момент вызова метода sort().
public class BubbleSorter {
private static BubbleSorter instance = null;
public static BubbleSorter getlnstance() {
synchronized (BubbleSorter.class) {
i f (instance
instance
null)
new BubbleSorter();
return instance;
public void sort(int.. args) {
//. .
public class Datastorage {
private int .. data;
public Datastorage(int[j data) {
Объектно-ориентированный анализ и прелраммирование
public i n t [ J g e tD a ta O
49
{
//. .
public v o id s o r t ( )
{
//. .
public v o id s o r t ( )
{
i f ( d a ta != null)
B u b b le s o rte r. g e tln s ta n c e () . s o r t ( d a ta );
public static v o id m ain ( S t r i n g (J a r g s )
{
//. .
Новая реализация BubbleSorter предусматривает наличие статического ноля instance, представляющего собой
ссылку на экземпляр класса BubbleSorter. С помощью статического метода getlnstanceO клиентские классы
могут получить доступ к полю instance. При этом работа всего класса основана на принципе ленивого
программирования: пока кому-либо не понадобится сущность BubbleSorter, экземпляр не будет создан.
Однако после первого обращения и выполнения инициализации ссылка на созданный объект будет сохранена в
иоле instance, и последующее обращение нс повлечет повторную инициализацию. Предлагаемая реализация с
технической точки зрения является более правильной, так как помимо прочего клиентские объекты получают
доступ к одному и тому же экземпляру BubbleSorter. Такое решение не сказывается на функциональности и
производительност и, однако, позволяет «нс плодить сущности без надобности».
Одиночка
Представленный пример имеет много общего с другим шаблоном проектирования — «одиночкой». Такое
название получил порождающий шаблон, который гарантирует, что у класса есть только один экземпляр, и
предоставляет к нему глобальную точку доступа. Важным является то, что работа осуществляется с
экземпляром класса, так как это позволят пользоваться расширенной функциональностью в сравнении со
статическими классами.
Положительными эффектами, связанными с применением «одиночки», является уменьшение числа имен в
адресном пространстве и предоставление глобальной точки доступа. Однако частое использование глобальных
объектов приводит к созданию немасштабирусмых приложений, а также усложняет написание комплексных
тестов.
Типичным
примером
использования
«одиночки»
является
логгер
—
специализированный
класс,
предназначенный для запоминания последовательности действий, совершаемых пользователем при работе с
приложением.
В случае неожиданного сбоя эта последовательность может быть использована для
воспроизведения проблемной ситуации с целью определения и устранения се причин.
import java.util.InputMismatchException;
import java.util.Scanner;
public class Logger {
Объектно-ориентированный анализ и программирование
50
private volatile static Logger instance;
public static Logger getlnstance() {
i f (instance
null)
{
synchronized (Logger.class)
i f (instance
null)
instance
{
{
new Logger();
return instance;
public void print(String arg)
{
System.out.printin(arg) ;
System.out.flush ();
public class Client {
public static void m a i n (String[] args)
Scanner in
{
new Scanner(System. in);
System.out.printin("Введите число: ");
try {
int x = in.nextlnt();
Logger.getlnstance().print("Пользователь ввел число
" + x) ;
catch
(InputMismatchException e) {
Logger.getlnstance().print("Пользователь ввел не
число");
В представленном примере класс Logger как раз и является тем «одиночкой», который позволяет
документировать события, происходящие в приложении. Метод getlnstance() необходим для получения ссылки
на экземпляр класса (используется ленивая инициализация). Метод print(Slring arg) может быть вызван из
любого участка приложения для вывода строки-аргумента на экран. Пример использования возможностей
Logger приведен в методе main класса Client.
Объектно-ориентированный анализ и профаммирование
51
Прототип
Последним рассматриваемым в данном разделе шаблоном будет «прототип» — порождающий шаблон
проектирования, который задает вид создаваемых объектов. Новые объекты создаются с помощью
копирования этого прототипа через клонирование, а не с помощью конструктора.
Абстрактно «прототип» может быть представлен в виде диаграммы классов.
Интерфейс
метод
Prototype
объявляет
клонирования
объектом
самого
себя.
Классы
ConcreteProtolypeA
и
Concrete Prototype В
реализуют
интерфейс Prototype. Client имеет
копию экземпляра прототипа и в
случае необходимости может создать
произвольное
количество
копий
Диаграмма шаблона «прототип»
данного класса посредством метода
makeCopyO.
Рассматриваемый
шаблон обладает
рядом
важных особенностей.
Во-первых,
«прототип» позволяет
специфицировать новые объекты путем изменения значений. Иными словами, поведение объекта может быть
изменено не с помощью создания нового экземпляра класса, а путем задания переменных объекта. Во-вторых,
использование шаблона способствует сокращению числа подклассов. Например, шаблон «фабричный метод»
часто порождает иерархию классов-создателей, параллельную основной иерархии классов-продуктов.
«Прототип» позволяет создавать новый объект через клонирование, а не через запрос к фабричному методу.
Таким образом, иерархия классов-создателей становится не нужной. В-третьих, с помощью «прототипа»
становится возможным динамическое конфигурирование приложения классами, то есть он позволяет
организовать динамическую загрузку классов в приложение во время его выполнения. Java имеет практически
встроенную поддержку данного шаблона. Дело в том, что в стандартную библиотеку JavaAPI входит
интерфейс Cloneable. Каждый экземпляр класса, реализующего этот интерфейс, может быть продублирован с
помощью метода clone(). Таким образом, для организации «прототипа» в собственном java-приложении нет
необходимости в описании отдельного интерфейса.
Структурные шаблоны
Адаптер
Наиболее
часто применяемым
на практике является
шаблон «адаптер» —
структурный
шаблон
проектирования, предназначенный для преобразования интерфейса одного класса в интерфейс другого класса.
Шаблон может помочь в тех случаях, когда возникает необходимость использовать готовый сторонний или
ранее разработанный класс, однако его интерфейс не соответствует потребностям. Иными словами, «адаптер»
позволяет повысить степень повторного используемост и кода.
Типичным примером шаблона может служить следующий код.
public class SequencesProcessor {
static boolean isNondecreasing(int[] arg, int length)
f o r (int i = 0; i < length - 1; i++)
if(arg.i_ > arg.i + 11)
return false;
return true;
{
Объектно-ориентированный анализ и программирование
52
static boolean isNonincreasing(int[I arg,
f o r (int 1 = 0 ;
int length)
{
i < length - 1; i++)
if(arg.i. < arg.i + 1])
return false;
return true;
}
public class Adapter {
public static boolean isMonotnic(int.. arg)
{
return SequencesProcessor.isNondecreasing(arg, arg.length)
SequencesProcessor.isNonincreasing(arg, arg._ength);
public class Client
{
public static void m a i n(String[] args)
{
inti] a = {1, 2, 5, 7, 15, 23);
inti] b = {5, 13, 51, 41, 45, 645};
int[] c = {78, 18, -10, -10};
i f (Adapter.isMonotnic(a))
System.out.printin("Sequence A is monotonic");
i f (Adapter.isMonotnic(b))
System.out.printin("Sequence В is monotonic");
i f (Adapter.isMonotnic(c))
System.out.printin("Sequence C is monotonic");
System.out.f_ush ();
Класс-библиотека SequencesProcessor декларирует и реализует методы для обработки последовательностей, в
частности — для проверки на невозростание и неубывание (isNonincreasinf и isNondecreasing). Параметрами
методов являются проверяемый массив целых чисел и его размер. Очевидно, что передача размера не является
необходимой операцией. Именно поэтому клиентский класс Client для проверки монотонности массива
взаимодействует с библиотекой через адаптер Adapter, который позволяет модифицировать сигнатуру
вызываемого метода.
Фасад
«Фасад» — структурный шаблон проектирования, предоставляющий общий простой интерфейс для доступа к
ряду модулей подсистемы. Зачастую сложные программные комплексы состоят из большого числа
компонентов. При этом некоторым разработчикам необходим доступ к отдельным подсистемам. «Фасад»
предлагает типовой сценарий взаимодействия, устраивающий большинство разработчиков, но при этом не
скрывает доступ к отдельным компонентам системы.
Объектно-ориентированный анализ и программирование
53
В сравнении с подходом модульной декомпозиции системы, данный шаблон обладает рядом важных
преимуществ. Во-первых, в большинстве случаев клиенты изолированы от подсистем, что приводит к
уменьшению числа объектов, с которыми приходится работать, и упрощает взаимодействие со всей системой в
целом. Во-вторых, использование «фасада» в целом облегчает устройство всей системы. Шаблон позволяет
ослабить зависимости между подсистемами и разложить всю систему на слабосвязанные слои. В-третьих,
«фасад» не препятствует клиентам работать непосредственно с отдельными компонентами. Таким образом,
клиент имеет выбор — работать ли непосредственно с подсистемами или с «фасадом».
Рассмотрим пример использования «фасада».
im p o rt j a v a . i o .* ;
im p o rt j a v a . u t i l . * ;
p u b l i c c l a s s D a ta F ile R e a d e r {
p u b l i c Integer[j dataReading(String filename) t h r o w s lOException
{
Scanner in
new Scanner (new FileReader(new
File(filename)));
Vector<Integer> data
new Vector<Integer>();
int n = in.nextlnt();
for(int i = 0; i < n; i++)
data.a d d (new Integer(in.n e x t l n t ( ) ) ) ;
in.close ();
Integer. ex
new Integerflj;
r e t u r n data.toArray(ex);
p u b lic c la s s B u b b le s o rte r {
p u b l i c void sort (Integer ..' args)
{
boolean flag = t r u e ;
w h i l e (flag)
{
flag = f a l s e ;
f o r (int i = 0; i < args.l e n g t h - 1; i++)
i f (args.i. > args.i + 11) {
flag
tru e ;
int tmp = args.i];
args[i] = args[i + 1];
args[i + 1] = tmp;
p u b lic c la s s D a ta F ile W rite r {
p u b l i c void dataWriter(String filename, Integer
data) t h r o w s
Объектно-ориентированный анализ и программирование
54
lOException {
Printwriter out
new Printwriter(filename) ;
out.printin(data._ength) ;
for(int i = 0; i < data.length; i++)
out.printin(data[i ]);
ou t .close();
public class Client {
private DataFileReader dfr
private BubbleSorter bs
private DataFileWriter dfw
new DataFileReader();
new BubbleSorter();
new DataFileWriter();
public void datalnFilesSorter(String. .. filenames)
for(int i = 0; i < filenames.length; i++)
{
{
try {
Integer.. data =
df r .dataReading(filenames.i . [0 ] );
b s .sort(data);
dfw.dataWriter(filenames[ij [1], data);
catch
(Exception e) {
e .printStackfrace();
}
public static void m a i n (String[] args)
Client cl
new Client();
String . . filenames = {{"l.in",
"2.out"},
{
"l.out"-,
{"2.in",
{"3.in", "3.out"}};
c l .datalnFilesSorter(filenames) ;
В представленном примере классы DataFileReader, BubbleSorter и DataFileWriter как раз и представляют собой
модули системы. Каждый из них обеспечивает соответствующий функционал: считывание данных с жесткого
диска компьютера, сортировка данных, запись результата на жесткий диск. Класс Client, являющийся
«фасадом», декларирует метод datalnFilesSorter(). Этот метод содержит описание наиболее частого сценария
действий в контексте рассматриваемой системы: считывание массива целых чисел из заданного файла,
сортировка и запись результата в заданный файл. Кроме того, метод ориентирован на групповую операцию, то
есть сценарий применяется последовательно к нескольким файлам.
Объектно-ориентированный анализ и программирование
55
Декоратор
Важным структурным шаблоном проектирования является «декоратор», который позволяет добавлять
объекту новые обязанности. Применение «декоратора» целесообразно в тех случаях, когда требуется
динамическое добавление обязанностей объектам, или когда требуется реализация обязанностей, которые в
дальнейшем могут быть сняты с объекта.
Очевидными достоинствами описанного
шаблона является, во-первых, большая
гибкость,
чем
при
статическом
наследовании, а во-вторых, упрощение
структуры отдельных классов. При этом
использование шаблона часто приводит к
«раздроблению системы» (значительному
возрастанию
объектов).
шаблон
количества
мелких
В некотором роде данный
—
альтернатива
порождению
подклассов.
Лучше
всего
принцип
работы
«декоратора» можно понять из диаграммы
классов.
Здесь Interface — это интерфейс, который
декларирует
базовой
метод
операции.
для
совершения
Component
Диаграмма шаблона «декоратор»
—
реализующий интерфейс Interface класс, на который возлагаются дополнительные обязанности. Decorator —
абстрактный класс, который хранит ссылку на объект Component и реализует operation! ) через делегирование.
ConcreteDecorator — класс-наследник от Decorator, который добавляет функциональности объекту Component.
Мост
Необходимым элементом более общего шаблона функционального дизайна является структурный шаблон
«мост», позволяющий отделить абстракцию от реализации таким образом, чтобы и то, и другое можно было
изменять независимо. «Мост» эффективен, прежде всего, в тех случаях, когда необходимо избегать
постоянной привязки абстракции к реализации (например, реализация должна быть выбрана во время
выполнения). Существенным преимуществом является то, что чаще всего изменение в коде реализации не
приводит к необходимости перекомпиляции кода клиента. Таким образом, иерархии классов, описывающих
абстракцию, и классов, отвечающих за поведение, могут изменяться независимо. Также в качестве достоинства
шаблона стоит отметить высокую степень инкапсуляции.
Диаграмма классов для шаблона «мост» представлен на рисунке.
Здесь абстрактный класс Abstraction
определяет
используемый
метод
operationf),
клиентами.
ConcreteAbstraction
Класс
наследует
от
класса Abstraction и непосредственно
используется клиентами. Интерфейс
Implementation предоставляет метод,
реализующий
Диаграмма шаблона «мост»
операцию
класса
Объектно-ориентированный анализ и программирование
56
Abstraction. ConcretelmplementationA и ConcretelmplementationB реализуют интерфейс Implementation.
Заместитель
Последним в этом разделе будет разобрано еще одно интересное архитектурное решение. Рассмотрим
следующую ситуацию.
Пусть в программе
используется
несколько «тяжелых» изображений,
и их
инициализация (загрузка графического представления их указанного файла) требует значительного времени и
системных ресурсов. Однако, эти изображения расположены не на главной форме и вообще отображаются не
одновременно. В таком случае хорошим решением является инициализация таких объектов по требованию.
Пусть в контексте рассматриваемого примера для корректной загрузки прочих компонентов требуется знать
размеры всех изображений (это может быть необходимо в случае сложной верстки). Именно для таких
ситуаций и предназначен шаблон «заместитель», который позволяет выполнять инициализацию объектов но
требованию, и кроме этого, обладает каким-либо непосредственно связанным с замещаемым объектом
дополнительным функционалом (в данном случае — возможностью сообщить реальные размеры изображения
без его загрузки).
Другими словами, возможна ситуация, когда нецелесообразно выполнять инициализацию всех объектов в
начале работы приложения, однако отложенная загрузка не является приемлемым решением. «Заместитель»
применяется в тех случаях, когда необходимо использовать не просто указатель на объект, но и
дополнительный функционал, связанный с этим указателем.
Структура шаблона приведена на диаграмме классов.
На диаграмме классы и интерфейсы
выполняют следующие роли. Subject
—
интерфейс,
предоставляющий
клиентам метод доступа к объектам.
ConcreteSubject
—
конкретный
субъект,
реализующий
Subject.
Proxy
—
интерфейс
«заместитель»
ConcreteSubject, хранящий на него
ссылку и реализующий интерфейс
Диаграмма шаблона «заместитель»
Subject. Помимо основного метода
rcqucstO, который переадресует запрос ConcreteSubject, обладает дополнительной функциональностью,
реализованной в метода otherFunctional().
Основным преимуществом шаблона является появление нового уровня функциональности при доступе к
объекту, при этом ряд операций приобретает отложенный характер, что в некоторых случаях может
значительно повысить общую производительность системы. Однако нс надо забывать, что использование
«заместителя» чревато увеличением времени отклика при обращении к объекту.
Поведенческие шаблоны проектирования
Цепочка ответс твенност и
Первым рассматриваемым в этом разделе архитектурным решением будет «цепочка ответственнос ти» —
поведенческий шаблон проектирования, предназначенный для связи объектов-обработчиков запросов в
цепочку и передачи запроса по ней до тех пор, пока он нс будет обработан. Шаблон предназначен для тех
ситуаций, когда конкретный обработчик запроса клиента заранее неизвестен и должен быть найден
автоматически. Помимо этого «цепочка ответственности» может оказаться удобной в ситуации, когда набор
объектов, способных обрабатывать запросы, должен динамически меняться.
Объектно-ориентированный анализ и нрофаммирование
57
Примером «цепочки ответственности» может являться следующая структура, предназначенная для хранения
нар целых чисел ключ-значение.
import java.util.HashMap;
public interface Finder {
public int getData(int key);
public void add(int key, int data);
}
public class SlowFinder implements Finder {
private HashMap<Integer, Integer> data;
SlowFinder() {
data
new HashMap<Integer, Integer>();
public int getData(int key) {
return data.get(key);
}
public void add(int key, int data) {
this.data.put(key, data);
}
public class FastFinder implements Finder {
private Finder SlowFinder;
private int.. data;
FastFinder(int size, Finder SlowFinder)
{
data = new int[size:;
this.s_owFinder = SlowFinder;
public int getData(int key) {
i f (key >= 0 && key < data.length)
return data[key];
else
return SlowFinder.getData(key);
}
public void add(int key, int data) {
if (key >= 0 && key < this .data ..length)
this.data.key. = data;
else
SlowFinder.add(key, data);
Объектно-ориентированный анализ и профаммирование
58
public class Client {
public static void main (String i
.' args)
Finder ff
ff.add(10,
{
new FastFinder(1000, new SlowFinder());
1);
ff.add(100,
2);
ff.adddOOO,
3);
ff.adddOOOO,
4);
f o r (int i = 10; i <= 10000; i = i * 10)
System.out.printin(ff.getData(i));
System.out.flush();
В данном примере классы SlowFinder и FastFinder реализуют интерфейс Finder. Первый класс использует для
хранения данных коллекцию HashMap, что позволяет хранить нары ключ-значение для широкого диапазона
ключей. Класс FastFinder использует для хранения пар линейный массив и является «точкой входа» в цепочку.
Операция доступа к элементам HashMap более трудоемкая, чем операция обращения к элементу массива,
поэтому при условии, что большая часть запросов будет обрабатываться экземпляром класса FastFinder,
предлагаемая цепочка позволит значительно сократить время, затрачиваемое в среднем на поиск одного
значения.
Важными
преимуществами
«цепочки
ответственности»
являются,
во-первых,
ослабление
связности
(клиентскому объекту нет нужды знать что-либо об объектах, обслуживающих его запрос; достаточно только
иметь ссылку на точку входа), а во-вторых, дополнительная гибкость при распределении обязанностей
(цепочка
классов-обработчиков
может быть модифицирована с
минимальными затратами).
Однако
необходимо помнить, что само по себе наличие цепочки не гарантирует обработку запроса (запрос может
пройти через всю цепочку объектов и при этом нс быть обработанным).
Итератор
Практически все современные реализации коллекций на большинстве языков программирования позволяют
работать с итераторами. Итератор — это способ последовательного (в общем случае) перебора элементов.
Например, стандартная реализация итератора для вектора элементов позволяет обращаться к первому,
последнему, следующему и предыдущему элементам. Осуществляется это благодаря использованию
одноименного шаблона.
«Итератор» — поведенческий шаблон, предоставляющий доступ ко всем элементам составного объекта, не
раскрывая его внутреннего представления.
Чаще всего «итератор» применяется,
когда необходимо
организовать несколько активных обходов одного и того же объекта или требуется поддержка единообразного
интерфейса с целью взаимодействия с составными структурами разной природы.
Пример использования шаблона представлен ниже.
import java.util.Vector;
public class Vertex implements Comparable<Vertex> {
private int key;
private int data;
Объектно-ориентированный анализ и программирование
59
Vertex(int key, int data) {
t h i s . key = key;
t h i s . s e t D a t a (data) ;
p u b l i c int getDataO
{
r e t u r n data;
}
p u b l i c void setData(int data)
{
t h i s . data = data;
p u b l i c int compareTo(Vertex argO)
{
i f ( t h i s . k e y < a r g O .k e y )
re tu rn
-1;
i f ( t h i s . key > argO.key)
re tu rn
re tu rn
1;
0;
p u b l i c a b s t r a c t c l a s s E n u m e r a to r {
p r o t e c t e d int position = 0;
p r o t e c t e d Heap heap;
Enumerator(Heap heap) {
t h i s . heap = heap;
p u b l i c Vertex current() {
r e t u r n heap.getVertex(position);
p u b l i c void first() {
position = 0;
p u b l i c void last()
{
position - heap.size() - 1;
p u b l i c boolean isFirstO
{
r e t u r n position == 0 ? t r u e
p u b l i c boolean isEnd()
{
: fa ls e ;
Объектно-ориентированный анализ и программирование
60
r e t u r n position < heap.size() ? f a l s e
: tru e ;
}
p u b l i c c l a s s H e a p i t e r a t o r e x t e n d s Enumerator {
Heapiterator(Heap heap)
s u p e r (heap);
{
}
p u b l i c void leftchild() {
position = ((position + 1) << 1) - 1;
}
p u b l i c void rightchild() {
position = (position + 1) << 1;
}
}
p u b lic in te r f a c e
Ite ra tio n
{
p u b l i c Enumerator createlterator ();
}
p u b l i c c l a s s H eap im p le m e n ts Iteration {
p r i v a t e Vector<Vertex> data;
p u b l i c int size()
{
r e t u r n data.size();
data
new Vector<Vertex>();
}
p u b l i c void add(Vertex argO)
{
//В этом методе выполняется добавление узла к куче
}
p u b l i c void remove(int key)
{
//В этом методе выполняется удаление узла из кучи
}
p u b l i c void clear() {
//В этом методе выполняется очистка кучи
}
p r i v a t e void upheap(int position)
{
//В этом методе осуществляется подъем элемента вверх
Объектно-ориентированный анализ и программирование
61
private void downheap(int position) {
//В этом методе осущетсвпяется спуск элемента вниз
protected Vertex getVertex(int position)
if (position < data.sizeO)
{
return data.elementAt(position);
else
return null;
public Enumerator createlterator() {
return new Heapiterator(this);
В предлагаемом примере частично реализован итератор для структуры данных «куча». Отличительной
особенностью этой разновидности бинарного дерева является то, что значение элемента-предка всегда не
меньше, чем значение его элементов-потомков. Класс Heap является абстракцией «кучи». Для хранения
элементов используется вектор. Heap реализует интерфейс Iteration и, соответсвенно, декларируемый в нем
метод getlteratorQ. Класс Heaplterator расширяет абстрактный класс Enumerator, в котором описаны методы
перемещения но вершинам дерева.
Хранитель
Простым, но чрезвычайно полезным поведенческим шаблоном является «хранитель». Назначение этой
структуры — фиксация и вынесение за пределы объекта его внутреннего состояния с целью его дальнейшего
восстановления. «Хранитель» необходим для мгновенного создания «снимка» объекта в тех случаях, когда
прямое получение такого «снимка» раскрывает детали реализации объекта и нарушает принцип инкапсуляции.
Иными словами, «хранитель» помогает выдерживать границы инкапсуляции и упростить структуру
класса-хозяина за счет того, что ему не приходится хранить внутри себя резервные внутренние состояния.
Пример «хранителя» представлен ниже.
import java.util.Random
public class Originator {
private int .. data;
Originator(int size) {
data = new int[size];
for(int i = 0; i < size; i++)
data.!. = i + 1;
public inti] getData()
{
return data.c-one();
private void swap(int posl, int pos2)
{
Объектно-ориентированный анализ и программирование
62
int tmp = data.posl.;
data.posl ( = data[pos2];
data.pos2. = tmp;
public void permutation() {
Random rnd
new Random();
fo r (int i = 0; i < 3 * data.length; i++)
{
int posl = rnd.nextint(data._ength);
int pos2 = rnd.nextlnt(data._ength);
swap(posl, pos2);
public Memento getStateO
{
return new Memento(data.clone());
public void setstate(Memento state)
{
data = state.restore();
public class Memento {
private int .. data;
Memento(int
argO)
{
data = argO;
public in t [J restore() {
return data.clone();
В примере класс Originator предоставляет метод для генерации перестановки чисел. Внутреннее состояние
класса (текущая перестановка) может быть сохранено с помощью метода getStateO как экземпляр класса
Memento. Если в процессе работы с классом Originator возникнет необходимость, то «снимок» может быть
использован для восстановления «удачной» перестановки (метод setStateQ).
Хотя использование «хранителя» чревато появлением значительных издержек при работе с программной
системой, его использование просто необходимо во многих случаях (например, для создания контрольных
точек восстановления системы).
Объектно-ориентированный анализ и нрофаммирование
63
Шаблонный метод
Одним из фундаментальных приемов повторного использования кода является «шаблонный метод» —
поведенческий шаблон, который определяет основу алгоритма и позволяет подклассам переопределять
некоторые шаги алгоритма. Наиболее значимое применение данный шаблон нашел в библиотеках классов, так
как он предоставляет возможность вынести общее поведение в отдельную сущность.
Структуру и принцип построения этого шаблона проще всего рассмотреть на следующем примере.
p u b lic a b s tr a c t c la s s S o rt
p riv a te
{
int[j data;
p u b l i c a b s t r a c t boolean compare(int x, int y) ;
p r i v a t e void swap(int posl, int pos2)
{
int tmp - data.posl.;
data.posl. = data pos2_;
data.pos2. = tmp;
p u b l i c int.. sort(int.. argO)
{
data = argO.clone();
boolean isSorted = f a l s e ;
w h ile
(!isSorted) {
isSorted
tru e ;
f o r (int i = 0; i < data.l e n g t h
1; i++)
i f (compare(data.i ., data.i +1]))
isSorted
swap(i, i
fa ls e ;
1) ;
r e t u r n data;
p u b l i c c l a s s A s c e n d i n g S o r t e x t e n d s Sort {
p u b l i c boolean compare(int x, int y) {
i f (x <= y)
re tu rn
fa ls e ;
re tu rn tru e ;
p u b l i c c l a s s D e s c e n d i n g S o r t e x t e n d s Sort {
p u b l i c boolean compare(int x, int y) {
if(x
<= y)
re tu rn tru e ;
re tu rn
fa ls e ;
{
Объектно-ориентированный анализ и программирование
64
public class Client {
private static void print(int.. arg)
{
fo r (int i = 0; i < arg.length; i-+)
System.out.print(arg.i . + " ");
System.out.printin();
System.out.flush();
public static void m a i n (String I. args)
int
{
a = {2, 5, 4, 1, 3);
in t .. b = a .clone ();
Sort sorterForA
new AscendingSort();
Sort sorterForB
new DescendingSort();
a = sorterForA.sort(a);
b = sorterForB.sort(b);
print(a);
print(b);
В данном примере абстрактный класс Sort определяет предназначенный для упорядочивания массива целых
чисел метод sort(). Для обмена элементов массива местами используется приватный метод swap(). Сравнение
элементов выполняется с помощью абстрактного метода compare. Потомки класса Sorter — AscendingSort и
DescendingSort — как раз и реализуют compareQ. От конкретной реализации метода зависит конечный итог
сортировки. Первый потомок упорядочивается массив по возрастания, второй — но убыванию.
Состояние
Отчасти близким к шаблонному методу является «состояние» — поведенческий шаблон проектирования,
позволяющий объекту варьировать свое поведение в зависимости от внутреннего состояния. Этот шаблон
способен заменить собой код с большим количеством условных операторов, в котором выбор ветви зависит от
членов объекта. При этом чаще всего механизмы изменения внутреннего состояния выносятся в отдельный
класс. Такой подход позволяет изолировать логику работы от реализации.
Пример реализация «состояния» представлен ниже.
public interface State {
public void shift(int .. data);
public class ShiftLeft implements State {
public void shift(int.. data)
i f (data 1= null)
{
{
int tmp = data[0];
fo r (int i = 0; i < data.length - 1; i++)
О бъектно-ориентированны й анализ и нроф ам м и рован и е
65
data.i. = d a t a ! + 11;
data data.length - 1] = tmp;
}
}
}
p u b lic
c la s s
S h iftR ig h t
im p le m e n ts
State {
void shift(int[J data)
p u b lic
i f (data != n u l l )
int tmp
{
{
data .data._ength - 1] ;
forfint i = data._ength - 1; i > 0; i--)
data.i. = data.i - 1];
data IC J = tmp;
}
p u b lic
c la s s
C o n te x t
{
p riv a te
in t .. data;
p riv a te
int state;
p riv a te
State shifter;
p u b lic
Context(int[I data)
{
t h i s . data = data.clone();
state = 0;
shifter
n e w ShiftLeftO;
}
p u b lic
void shift() {
shifter. s h i f t (data);
}
p u b lic
void changestate() {
i f (state == 0)
shifter
n e w ShiftRight();
shifter
n e w ShiftLeftO;
e ls e
state = 1 - state;
p u b lic
void print() {
f o r (int i = C; i < data.length;
i++)
System.out.print(data.i . + " ");
System.out.printin() ;
Объектно-ориентированный анализ и нрофаммирование
66
Интерфейс State декларирует метод shift)), осуществляющий циклический сдвиг элементов массива. Классы
ShiftRighl и ShiftLcft реализуют интерфейс State и позволяют выполнять сдвиг вправо и влево соответсвенно.
Экземпляр класса Context содержит влияющий на поведение член state: если состояние тождественно равно О,
то при вызове метода shift() произодйет сдвиг хранимых данных влево, иначе — вправо. За изменение
состояния объекта отвечает метод changeStateQ.
Посредник
Сложные системы содержат огромное число классов. Для того, чтобы система функционировала, классы
должны взаимодействовать друг с другом. Часто такая необходимость приводит к тому, что вся система
«обрастает»
огромным
числом
ссылок.
Поведение
такой
системы
в
дальнейшем
очень
сложно
модифицировать. Более того, такой код не может быть повторно использован. Для решения описанной
проблемы предназначен шаблон «посредник», который позволяет организовывать взаимодействие других
классов
«через
себя».
Иными
словами,
«посредник»
—
поведенческий
шаблон
проектирования,
предназначенный для обеспечения слабой связности системы (избавление объектов от необходимости
ссылаться друг на друга).
Использование «посредника» обеспечивает ряд преимуществ. Во-первых, устраняется связность между
подклассами системы. Во-вторых, упрощаются протоколы взаимодействия между компонентами. И наконец,
в-третьих, «посредника» позволяет централизовать управление.
Примером использования «посредника» является следующий код.
import java.util.Vector;
public class Array {
private Vector<Integer> data;
private Mediator m;
Array(Mediator m) {
this. IT. = m;
data
new Vector<Integer>();
public void add(int argO) {
data.add(argO);
m.update(argO);
}
public class Maximal {
private int maximum = -1;
private Mediator m;
Maximal(Mediator m) {
this.m = m;
rn.setMaxim.a_ (this) ;
}
public void setMaximum(int argO) {
Объектно-ориентированный анализ и программирование
67
i f (maximum < argO)
{
maximum = argO;
m .p r i n t U p d a t e (argO);
p u b lic c la s s P r in te r
Printer(Mediator m) {
m .s e t P r i n t e r ( t h i s ) ;
}
p u b l i c void printAdd(int argO)
{
System.out . p r i n t i n ("Add number " + argO);
p u b l i c void printMaximum(int argO)
{
System.o u t . p r i n t i n ("Maximum now is " - argO);
p u b l i c c l a s s M e d ia to r {
p r i v a t e Maximal m;
p r i v a t e Printer p;
p u b l i c void update(int argO)
{
i f (p != n u l l )
p .printAdd(argO);
i f (m != n u l l )
m .setMax imum(argO);
p u b l i c void printUpdate(int argO)
{
i f (p != n u l l )
p.printMaximum(argO);
p u b l i c void setPrinter(Printer p) {
th is .p
= p;
p u b l i c void setMaximal(Maximal m) {
th is .m
}
= m;
Объектно-ориентированный анализ и прсяраммирование
68
В представленном примере система включает в себя три класса: Array, Maximal и Printer. Первый класс
осуществляет хранение массива данных и реализует функции доступа к нему. Второй класс предоставляет
методы для взаимодействия и обработки данных (в данном случае — определение и хранение максимального
элемента). Третий класс необходим для локирования изменений. Взаимодействие между блоками системы
реализуется с помощью класса-посредника Mediator, который является членом Array и Maximal. Такое
решение гарантируется слабую связность между компонентами. Ни одному элементу системы неизвестно
ничего про остальные. Всю работу ио связыванию выполняет Mediator.
Команда
Последним рассматриваемым в данном разделе шаблоном будет «команда» — поведенческий шаблон
проектирования, предназначенный для обработки запросов как объектов. Он позволяет парамсгризировать
объекты выполняемым действием, ставить запросы в очередь, отменять запросы, протоколировать
проделанные изменения и так далее. Иными словами «команда» — своеобразная объектная обертка для
пользовательских
запросов.
С
его
помощью
может
высокоуровневых операций легко изменяемая система.
Ниже представлен пример реализации данного шаблона.
public interface Command {
public void execute(int x, int y) ;
public class FlipUp implements Command {
private Grid grid;
FiipUp(Grid grid) {
this.grid = grid;
}
public void execute(int x, int y) {
grid.flipUp(x, y) ;
public class FlipDown implements Command
private Grid grid;
FlipDown(Grid grid) {
this.grid = grid;
public void execute(int x, int y) {
grid.flipDown(x, y);
}
public class Invoker {
public Command cFlipUp;
public Command cFlipDown;
быть
создана
структурированная
на
основе
О бъектно-ориентированны й анализ и проф ам м и рован и е
Invoker(Command flipUp,
th is .c F lip U p
69
Command flipDown)
{
= flipUp;
t h i s . cFlipDown = flipDown;
}
p u b lic
void f l i p U p (int x, int y)
{
t h i s . cF.ipUp.exec ut e (x, y ) ;
}
p u b lic
void flipDown(int x, int y)
{
t h i s . cFlipDown.e x e c u t e (x, y ) ;
p u b lic
c la s s
G rid
{
p riv a te
b y t e f I [, data;
p riv a te
s ta tic
int[] dx = {-1, 0, 1, 0};
p riv a te
s ta tic
int..
Grid()
dy = {0, 1, 0, -1};
{
data = n e w b y t e [4][4];
p u b lic
void f l i p U p (int x, int y)
{
i f (x > -1 && x < 4 && у > -1 && у < 4)
{
d a t a . x . .у. = 1;
f o r (int i = 0; i < 4; i++)
flip(x + dx.i],
у + dy.i]);
}
}
p u b lic
void flipDown(int x, int y)
{
i f (x > -1 && x < 4 && у > -1 && у < 4)
{
data ,x. .у. = 0;
f o r (int i = 0; i < 4; i++)
flip(x + dx.i.,
у + dy.i.);
}
p riv a te
void f l i p (int x, int y)
i f (x >
data[xj .y.
p u b lic
{
&& x < 4 && у > -1 && у < 4)
(byte)
(1 - data.x. .y_);
void pri n t s t a t e () {
f o r (int i = 0; i < 4; i++)
{
Объектно-ориентированный анализ и программирование
for(int j = 0;
j < 4;
70
j++)
S y ste m. o u t . p r i n t ( d a t a .i . .j. );
S y s t e m . o u t . p r i n t i n () ;
В данном примере класс Grid представляет собой абстракцию квадратной доски размером четыре на четыре
ячейки, в каждой из которых может находиться либо 0, либо 1. Состояние любой ячейки может быть
изменено, однако такое действие повлечет изменение состояний соседних клеток. Классы FlipUp и FlipDown
обеспечивают взаимодействие с элементами доски. Операции, реализованные в них, и отвечают за изменения
состояний. В контексте примера именно они и являются объектами-командами. Класс Invoker аккумулирует в
себе операции и предоставляет клиенту единую точку доступа ко всем возможным сценариям.
Концепция МУС как результат применения объектно-ориентированного анализа и
проектирования
Одним из наиболее наглядных и понятных практических примеров мощности объектно-ориентированной
парадигмы является концепция MVC. В связи с быстрым ростом темпов развития компьютерной инженерии и
резким увеличением сложности решаемых задач в конце 1970-ых потребовался гибкий способ для
разграничения программной логики и визуализации. Именно эту цель и преследовала предложенная в 1979
году MVC.
MVC предполагает разделение данных, представления данных и обработку действий пользователя на три
компонента: модель, представление и контроллер. Модель предоставляет данные предметной области и
методы работы с ними. При этом она не содержит никакой информации о том, как эти данные
визуализируются. Представление отвечает за визуализацию информации. Типичным примером представления
является графический интерфейс пользователя. Контроллер же обеспечивает связь между пользователем и
системой: осуществляет проверку введенных данных и использует модель и представление для реализации
необходимой реакции.
На рисунке представлено схематическое изображение концепции MVC. Сплошными линиями показаны
прямые связи (вызов функций, передача данных), пунктирными — косвенные (через сообщения).
Представление и контроллер зависят от
модели, однако модель от них никоим
образом
достигается
не
зависит.
возможность
Тем
самым
работать
с
моделью независимо от се визуального
представления.
Принято выделять две модели MVC:
• Пассивная модель. Модель нс влияет
на представление и контроллер, а
используется ими только в качестве
источника данных для отображения.
Все изменения модели отслеживаются
контроллером. Контроллер отвечает за
перерисовку, если это необходимо.
• Активная модель. Модель оповещает и представления о том, что в ней произошли изменения.
Заинтересованные в конкретных изменениях представления подписываются на оповещение.
Объектно-ориентированный анализ и прелраммирование
71
Использование концепции МУС позволяет добиться следующих преимуществ.
• Возможность присоединения к одной модели нескольких видов без модификации самой модели (одни и те
же данные могут быть представлены и таблицей, и диаграммой).
• Возможность присоединения к одному виду разных контроллеров, что позволяет получить разную реакцию
на действия пользователя.
• Более высокий уровень разделения труда программистов. Разработчики, занимающиеся логикой, нс
связаны с разработкой представления.
• Повышение степени повторного использования кода.
В то же время концепция относительно сложна для понимания, что зачастую вызывает ее неправильное
использование. Кроме того, в некоторых случая MVC приводит к чрезмерному усложнению разрабатываемой
архитектуры.
Примечательно то, что МУС практически полностью «составлена» из шаблонов проектирования. Основными с
архитектурной точки зрения являются «наблюдатель», «стратегия», «команда», «компоновщик» и «фабричный
метод». Разберем подробнее ранее нс рассмотренные шаблоны. «Наблюдатель» — это шаблон поведения
объектов, определяющий связь «один ко многим» таким образом, что при изменении состояния одного
объекта все объекты, зависящие от него, оповещаются об этом изменении и автоматически обновляются.
Данный шаблон позволяет обеспечить согласование состояний объектов без жесткой связи классов, что
повышает повторную используемость кода.
Структура шаблона приведена на рисунке. Участниками являются субъект (Subject), наблюдатель (Observer),
конкретный субъект (ConcreteSubject), конкретный наблюдатель (ConcretcObscrver). Субъект является
интерфейсом, предоставляющим методы для присоединения и отделения наблюдателей. Помимо этого он
также аккумулирует информацию обо всех наблюдателях. Конкретный субъект — это класс, реализующий
интерфейс Subject. Экземпляр Subject содержит некоторое состояние, представляющее интерес для
конкретного наблюдателя, и декларирует методы для оповещения наблюдателей <х> изменении этого
состояния. Наблюдатель — интерфейс для обновления состояния объектов-наблюдателей. Конкретный
наблюдатель хранит ссылку на субъект и значение состояния субъекта, за которым осуществляется
наблюдение. Помимо этого конкретный наблюдатель реализует интерфейс Observer.
Данный
шаблон
обладает
целым
рядом достоинств и особенностей,
среди
которых
особенно
следует
выделить следующие.
• Абстрактная связанность субъекта
и наблюдателя. Субъекту известно
только то, что у него есть ряд
наблюдателей. Он не знает ни их
типов, ни положения в иерархии
классов. Таким образом, связь
между субъектом и
state = subject getStateQ
наблюдателями сведена к
минимуму и носит абстрактный
return state
характер.
• Повышение повторной
Диаг рамма шаблона «наблюдатель»
используемости кода. За счет
высокой степени абстракции шаблон обладает высокой степенью повторной используемости.
• Обеспечение широковещательной коммуникации. Уведомление об изменения автоматически поступают
всем наблюдателям, подписавшимся на него. При этом для субъекта нс имеет значения, проигнорирует ли
Объектно-ориентированный анализ и программирование
72
конкретный наблюдатель это сообщение или обработает.
«Стратегия» — поведенческий шаблон проектирования, определяющий семейство алгоритмов. В некоторых
ситуациях программа должна обеспечивать различные варианты поведения разных экземпляров одного класса.
Иногда поведение требуется менять на стадии выполнения. Решение данной задачи возможно с помощью
отделения процедуры выбора алгоритма от его реализации.
Как следует из представленной на диаграмме структуры, этот шаблон имеет много общего с «мостом».
Context
strategy
«Strategy»
+ strategy Strategy
algorithm©
+ executeO
Здесь
Strategy
—
интерфейс
реализации
алгоритма.
ConcretcStrategy
реализует
интерфейс Strategy. Класс Context
конфигурируется объектом
ConcreteStrategyA
ConcreteStrategyB
+ algorithm©
+ algonthmQ
strategy algonthmO
Диаграмма шаблона «стратегия»
класса
ConcretcStrategy и хранит ссылку на
объект класса Strategy. В некоторых
случаях Context может определять
интерфейс, позволяющий получить
объектам класса Strategy доступ к
своим данным.
Шаблон также обладает некоторыми важными свойствами. Во-первых, «стратегия» позволяет с легкостью
использовать семейство алгоритмов, предотвращая порождение большого числа подклассов. Естественно, что
от класса Context можно породить группу подклассов с разным поведением, однако такое решение приведет к
затруднению понимания иерархии. Во-вторых, данный шаблон позволяет избавиться от условных операторов,
определяющих стратегию поведения. Наконец, в-третьих, «стратегия» позволяет определять несколько
реализаций одного и того же алгоритма. Клиент может динамически выбирать конкретную реализацию в
зависимости от ситуации или объема свободных ресурсов.
Однако, «стратегия», как правило, значительно увеличивает число объектов в адресном пространстве. Кроме
того, отчасти этот шаблон противоречит принципу инкапсуляции, так как клиент должен обладать
информацией о различиях в стратегиях. Кроме того, стратегии могут различаться «по сложности». Простой
стратегии могут не требоваться данные от клиента, а сложной понадобится много дополнительной
информации. В этом случае простой стратегии будет предана избыточная информация, что приведет к
повышению накладных расходов.
Последний, еще не рассмотренный, шаблон — «компоновщик», — объединяющий объекты в древовидную
структуру для представления иерархии от частного к целому. Основным назначением шаблона является
предоставление одинакового доступа как к объектам, так и к группам объектов.
Структура «компоновщика» изображена на диаграмме классов.
Объектно-ориен тированный анализ и профаммирование
73
Component предоставляет интерфейс для
компонуемых
объектов
и
определяет
общую для всех классов операцию по
умолчанию.
Leaf
является
листовым
узлом композиции и нс имеет потомков
(определяет
объектов
поведение
примитивных
композиции).
Composite
является составным объектом (хранит
объекты-потомки
и
реализует
относящиеся к управлению операции).
Также Composite перенаправляет запросы
на обработку своим потомкам.
«Компоновщик»
Диаграмма шаблона «компоновщик»
позволяет
определить
иерархию классов из примитивных и
составных
объектов,
что
значительно
упрощает профаммную архитектуру. С помощью данного шаблона приложение может однообразно работать
как с простыми, так и с составными объектами. Помимо этого значительно упрощается процедура добавления
новых видов компонентов, так как отпадает необходимость в переписывании кода приложения.
Наиболее существенным недостатком является невозможность офаничить типы используемых примитивных
объектов. В некоторых ситуациях необходимо, чтобы в состав контейнера входили только определенные типы
примитивов. Предлагаемое решение нс позволяет пользоваться для реализации таких офаничений
статической системой типов.
Теперь, после рассмотрения лежащих в основе МУС шаблонов, можно обсудить общую схему взаимодействия
компонентов.
Наиболее
типичная
реализация
концепции МУС изолирует модель
от вида с помощью установления
между
ними
протокола
взаимодействия на основе аппарата
оповещений.
изменение
модели,
Когда
внутренних
модель
происходит
данных
оповещает
в
все
представления, связанные с ней, с
помощью шаблона «наблюдатель».
При обработке реакции пользователя
вид выбирает нужный контроллер
для
обеспечения
той
или
иной
реакции. Выбор осуществляется с
помощью шаблона «стратегия». Однотипная работа с подобъектами сложно-составного вида обеспечивается
благодаря использованию шаблона проектирования «компоновщик».
Объектно-ориентированный анализ и программирование
Контрольные вопросы по Теме 4
1. Как Вы понимаете термин «шаблон проектирования»?
2.
Какие группы шаблонов проектирования выделяют? Для решения каких целей предназначены шаблоны
каждой из групп?
3.
Какие основные шаблоны проектирования Вы знаете? Для решения каких задач они предназначены?
4.
Каким правилам нужно следовать для реализации неизменяемого объекта в Java?
5.
В чем отличие «фабричного метода» от «строителя»?
6.
В чем заключается суть идеи отложенной операции?
7.
В чем заключается принцип отложенной инициализации?
8.
В каких случаях, кроме логирования, целесообразно применять шаблон «одиночка»?
9.
В чем назначение модификатора volatile?
10.
Почему в Java легче, чем в других языках программирования, можно организовать структуру, схожую с
шаблоном «прототип»?
11.
В чем заключается принципиальное отличие между шаблонами «адаптер» и «делегирование»?
12.
Для решения каких задач может быть использован «заместитель»?
13.
Где наиболее часто применяется «шаблонный метод»? Почему?
14.
Изобразите диаграммы классов для шаблонов состояние, посредник, команда, шаблонный метод в
соответствии со стандартом UML.
15.
Что такое MVC и какую цель преследовали разработчики концепции?
16.
В чем отличие пассивной и активной моделей MVC?
17.
Изобразите схему взаимодействия компонентов MVC.
18.
Какие преимущества и недостатки в использовании шаблонов проектирования Вы видите?
Выводы после Темы 4
• Шаблоны проектирования — универсальные архитектурные решения, позволяющие просто решать
сложные организационные задачи.
• В основе шаблонов проектирования лежат принципы объектно-ориентированного проектирования.
Объектный подход позволяет создавать мощные и гибкие решения, пригодные для многократного
использования.
• Примером использования объектного подхода является концепция MVC.
Заключение
Подведем некоторые итоги.
В процессе освоения дисциплины обучающиеся:
• познакомились с основными понятиями и идеями объектно-ориентированной парадигмы
программирования;
• научились описывать структуры программных комплексов с помощью графических средств;
• на базовом уровне освоили язык программирования Java;
• изучили типовые способы решения архитектурных задач, возникающих в процессе разработки
программного обеспечения.
Автор учебного пособия надеется, что представленный материал помог обучающимся понять суть
объектно-ориентированной парадигмы, осознать «выразительную мощность» этого подхода к проектированию,
позволяющего легко и просто оперировать гигантскими объемами разнотипной информации, удобным и
наглядным образом представлять структуры сложных систем.
74
Объектно-ориентированный анализ и программирование
75
Умения и навыки, полученные в ходе изучения курса, будут полезны при анализе структур объектов
различной природы и проектировании сложных программных продуктов.
Автор будет благодарен за все замечания по содержанию и стилю изложения материалов. Остается пожелать
дальнейшего
плодотворного
саморазвития.
Изучайте
тонкости
объектно-ориентированного
программирования, эти знания непременно пригодятся Вам в профессиональной деятельности!
Дополнительные материалы
Тип дополнительного
Описание и соответствие разделам учебника
Ссылка на
материала
Диаграммы классов
материал
Исходные файлы диаграмм классов, представленных в теме «Шаблоны
[15]
проектирования»
Исходные коды примеров
Исходные файлы примеров, представленных в теме «Шаблоны проектирования»
Практическое задание
http://edu.geiti.ru/
Тест
http://edu.geiti.ru/
Примечания
[1]
http://www.cyberforum.ru/oop/ll
[2]
http://iguania.ru/forum-programmistov-16.html
[3]
http://www.cyberforum.ru/uml/ll
[4]
http://www.uml2.ru/forum/index.phpll
[5]
http://javatalks.ru/branches/9ll
[6]
http://www.javenue.info/themes/ood/
[7]
http://www.rsdn.ru/summary/864.xml
[8]
http://www.intuit.ru/department/pl/javapl/
[9]
[
[11]
[
http://www.interface.ru/home.asp?artld=1602
10] http://www.java-study.ru/samouchitel
http://cs.mipt.ru/wiki/index.php/Объектно-ориентированное_программирование
12] http ://younglinux. info/book/ export/html/36
[13]
http://www.codenet.ru/progr/vbasic/oop.php
[14]
http://www.biblioclub.ru/54745_Yazyk_programmirovaniya_Java_.html
[15]
[
http://edu.geiti.ru/mod/resource/view.php?id=2848
16] http://edu.geiti.ru/mod/resource/view.php?id=2849
[16]
Источники и основные авторы
Источники и основные авторы
Объектно-ориентированный анализ и программирование Источник-. http://wiki.geiti.ru/index.php?oldid=23570 Редакторы-. G.chistyakov, НИКИПШЕ
Источники, лицензии и редакторы
изображений
Файл:1_1.РМС Источник-, http://wiki.geiti.ru/index.php?П11е=Файл:1_1.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:1_2.РМС Источник-, http://wiki.geiti.ru/index.php?1й1е=Файл: 1_2.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:1_З.РМС Источник-, http://wiki.geiti.ru/index.php?1й1е=Файл: 1_3.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:1_4.РМС Источник-, http://wiki.geiti.ru/index.php?1й1е=Файл: 1_4.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:3_1.РМС Источник-, http://wiki.geiti.ru/index.php?1й1е=Файл:3_1.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:3_2.РМС Источник-, http://wiki.geiti.ru/index.php ?title=Фaйл:3_2.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:3_З.РМС Источник-, http://wiki.geiti.ru/index.php ?title=Фaйл:3_З.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:3_4.РМС Источник-, http://wiki.geiti.ru/index.php ?title=Фaйл:3_4.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:3_5.РМС Источник-, http://wiki.geiti.ru/index.php ?title=Фaйл:3_5.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:3_6.РМС Источник-, http://wiki.geiti.ru/index.php ?title=Фaйл:3_6.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Фапл:4 1.рп§ Источник-. Ьпр:/^к1^еШ .ги/йИех.рЬр?П11е=Файл:4_1.р^ Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_1.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_l.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_2.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_2.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_9.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_9.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_З.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_З.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_4.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_4.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_5.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_5.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_6.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_6.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_7.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_7.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Файл:5_8.РМС Источник-, http://wiki.geiti.ru/index.php?title=Фaйл:5_8.PNG Лицензия-, неизвестно Редакторы-. G.chistyakov
Download