- •Теоретические основы объектно-ориентированного программирования
- •Объектная декомпозиция
- •Диаграмма обслуживания автомашин на бензоколонке
- •Диаграмма объектов имитационной модели бензоколонки
- •Объектная декомпозиция Блока колонок: 1 - когда освободится колонка? 3 - колонка свободна? 2 - освободить колонку 4 - занять колонку
- •Диаграмма объектов графического редактора
- •Диаграмма состояний интерфейса пользователя программы «Записная книжка»
- •Диаграмма объектов предметной области программы «Записная книжка»
- •Обозначение ассоциации: а - с указанием имени ассоциации и ее направления; б - с указанием имен ролей; в - с указанием множественности
- •Контекстная диаграмма классов программы «Записная книжка»
- •Объекты и сообщения
- •Типы отношений между объектами
- •Соответствие объекта-абстракции классу и объектам-переменным
- •Основные средства разработки классов
- •Иерархии классов при различных видах наследования
- •Иерархия классов Окно и Окно_меняющее_цвет
- •Иерархия классов Окно и Окно_с_текстом
- •Иерархия классов при сложном полиморфизме
- •Необходимость позднего связывания
- •Реализация механизма позднего связывания
- •Необходимость явного указания типа объекта, адресуемого указателем родительского класса
- •Вид окна сообщения
- •Структура полей класса Сообщение
- •Дополнительные средства и приемы разработки классов
- •Организация стека вызовов
- •Определение класса
- •Организация списка объектов с использованием статических компонентов класса
- •Конструкторы и деструкторы
- •Наследование
- •Иерархия классов Целое число - Вещественное число
- •Диаграмма классов с наследованием от двух классов и объектным полем
- •Иерархия с многократным наследованием
- •Исключения
- •1. Динамическая проверка типа объекта:
- •2. Динамическое переопределение типа объекта:
- •3. Динамическое определение типа объекта:
-
Дополнительные средства и приемы разработки классов
Рассмотренные в § 7.2 средства разработки классов являются основными. Они предусматриваются принципами ООП. Однако к настоящему моменту постепенно складывается более сложная модель ООП, включающая такие понятия, как метаклассы, контейнерные классы, делегирование методов, параметризованные классы. Появление в языках программирования соответствующих средств позволяет создавать более эффективные программы.
Метаклассы. Дальнейшее развитие идеи реализации полиморфных объектов привело к появлению более высокого уровня абстракции - метаклассов. Метакласс - тип, значениями которого являются классы как ссылки на типы. Переменные типа метакласса можно использовать вместо явного указания класса в соответствии с традиционными правилами организации косвенного доступа (9).
-
Прямое и косвенное обращения к классу
Реализация метаклассов базируется на использовании специальных таблиц, в которых хранится информация о классе: имя класса, имя класса-родителя, адреса методов и т. д. Поскольку эти таблицы используются во время выполнения программы, они получили название RTTI (Run Time Type Information - «информация о типе времени выполнения»).
Эти же таблицы используются для реализации следующих операций:
• операции проверки принадлежности объекта заданному классу или его потомкам;
• операции уточнения класса объекта, которая отличается от операции явного переопределения типа тем, что перед переопределением выполняется проверка принадлежности объекта данному классу или его потомкам;
• специальных операций определения, модификации или проверки свойств класса (как типа) - для реализации этих операций язык программирования должен предоставлять возможность описания полей и методов класса, обращение к которым возможно при отсутствии объектов класса.
Делегирование методов. Делегирование - «заимствование» методов у объектов других классов. Оно представляет собой альтернативу переопределению методов, используемому полиморфными объектами. В отличие от переопределения, делегирование позволяет определять различное поведение объектов, принадлежащих одному классу. Причем заимствование методов возможно как в пределах класса или иерархии классов, так и у объектов классов других иерархий.
Метод при этом вызывается косвенно, через указатель на него. Язык, в котором реализуется делегирование, должен обеспечивать возможность определения указателей на методы. Назначение или замена метода осуществляется присваиванием соответственного значения соответствующему указателю на метод (10).
-
Делегирование метода
Целесообразно различать статическое и динамическое делегирование. При статическом делегировании соответствующий указатель инициализируется в процессе компиляции программы и при выполнении программы не меняется. При динамическом делегировании значение указателю присваивается в процессе выполнения программы и может изменяться в зависимости от ситуации.
Статическое делегирование используют в двух случаях:
• если требуемое поведение объекта уже описывалось для объектов другого класса - в этом случае делегирование позволяет избавиться от повторов в программе;
• если класс объявлен с не полностью определенным поведением объектов (часто так описываются библиотечные классы) и его поведение уточняется для конкретных экземпляров объектов.
Примечание. Язык C++ в первом случае позволяет использовать множественное наследование, но оно может оказаться неэффективным, поскольку вместе с желаемым поведением придется наследовать и все остальные элементы класса.
Динамическое делегирование используется при создании объектов с изменяемым поведением, когда конкретный вариант поведения определяется некоторыми внешними обстоятельствами.
Пример 1.15. Делегирование методов (класс Фигура). В примере 1.1Пример 4.1 рассматривалась объектная декомпозиция простейшего графического редактора, позволяющего рисовать окружности и квадраты с изменяющимся размером, цветом и положением. В результате этой декомпозиции были получены три объекта: Монитор, Круг, и Квадрат (см.1Рис. 1.5.). Те же действия будет выполнять программа, при разработке которой использован объект с динамическим поведением. Объектная декомпозиция графического редактора в этом случае будет включать два объекта - Монитор и Фигура (11).
-
Объектная декомпозиция графического редактора
Вариант 1. Класс Фигура описан без указания конкретного варианта выводимой фигуры.
Класс Фигура1:
реализация
поля X, Y, Color
поле Адрес_метода_рисования
интерфейс
конструктор Создать(аХ,аY,аR,аСо1оr,аАдрес_метода_рисования)
метод Изменить_цвет(аСо1оr)
метод Изменить_размер(aR)
метод Изменить_местоположение(aX,aY)
Конец описания.
Адрес конкретного метода рисования должен указываться при создании соответствующего объекта. Изменение этого адреса в процессе выполнения программы не предусмотрено. В данном случае используется статическое делегирование.
Вариант 2. Возможен другой вариант: класс Фигура содержит метод, который меняет используемую процедуру рисования.
Класс Фигура2:
реализация
поля х, у, r, Color
поле Адрес_метода_рисования
интерфейс
конструктор Создать (ах, ay, ar, aColor,аАдрес_метода_рисования)
метод Изменить_цвет (aColor)
метод Изменить_размер (aR)
метод Изменить_местоположение (аХ, aY)
метод Изменить_тип_фигуры (аАдрес_метода_рисования)
Конец описания.
Выполнив указанный метод для объектов данного класса, можно изменить процедуру рисования в процессе выполнения программы - динамическое делегирование.
Сами методы рисования могут быть определены как в том же, так и в другом классе, например:
Класс Методы_рисования:
интерфейс
метод Рисование_окружности(аХ, aY, aR, aColor)
метод Рисование_квадрата(аХ, aY, aR, aColor)
Конец описания.
При определении метода в другом классе, к сожалению, приходится в явном виде передавать все необходимые параметры рисования, так как в противном случае последние будут недоступны. При определении делегируемых методов в своем классе этой проблемы не возникает.
Вариант 3. В этом случае методы рисования определены в самом классе:
Класс ФигураЗ:
реализация
поля X, Y, R, Color
поле Адрес_метода_рисования
метод Рисование_окружности
метод Рисование_квадрата интерфейс
конструктор Создатъ(аХ, aY, aR, aColor, аТип_фигуры)
метод Изменить_цвет (aColor)
метод Изменить_размер (aR)
метод Изменить_местоположение (аХ, aY)
метод Изменить_тип_фигуры (аТип_фигуры)
Конец описания.
Параметр аТип_фигуры будет получать значение, определяющее подключаемый к объекту метод рисования.
При использовании делегирования необходимо следить, чтобы заголовок метода соответствовал типу указателя, используемого для хранения адреса метода.
Контейнерные классы. Контейнеры - это специальным образом организованные объекты, используемые для хранения объектов других классов и управления ими. Для реализации контейнеров разрабатываются специальные контейнерные классы. Контейнерный класс обычно включает набор методов, позволяющих выполнять некоторые операции как с отдельным объектом, так и группой объектов.
В виде контейнеров, как правило, реализуют сложные структуры данных (различные виды списков, динамических массивов и т. п.). Разработчик наследует от класса-элемента класс, в который добавляет нужные ему информационные поля, и получает требуемую структуру. При необходимости он может наследовать класс и от контейнерного класса, добавляя к нему свои методы (12).
-
Построение классов на базе контейнерного класса и класса элемента
Контейнерный класс обычно включает методы создания, добавления и удаления элементов. Кроме того, он должен обеспечивать поэлементную обработку (например, поиск, сортировку). Все методы программируются для объектов класса-элемента. Методы добавления и удаления элементов при выполнении операций часто обращаются к специальным полям класса-элемента, используемым для создания структуры (например, для односвязного списка - к полю, хранящему адрес следующего элемента).
Методы, реализующие поэлементную обработку, должны работать с полями данных, определенными в классах-потомках класса-элемента.
Поэлементную обработку реализуемой структуры можно осуществлять двумя способами. Первый способ - универсальный - заключается в использовании итераторов, второй - в определении специального метода, который содержит в списке параметров адрес процедуры обработки.
Теоретически итератор должен обеспечивать возможность реализации циклических действий следующего вида:
<очередной элемент>:=<первый элемент>
цикл-пока <очередной элемент> определен
<выполнить обработку>
<очередной элемент>:=<следующий элемент>
все-цикл
Поэтому обычно он состоит из трех частей: метод, позволяющий организовать обработку данных с первого элемента (получение адреса первого элемента структуры); метод, организующий переход к следующему элементу, и метод, позволяющий проверить окончание данных. Доступ к очередной порции данных при этом осуществляют через специальный указатель текущей порции данных (указатель на объект класса-элемента).
-
Контейнерный класс с итератором (класс Список). Разработаем контейнерный класс Список, реализующий линейный односвязный список из объектов класса Элемент, описанных следующим образом:
Класс Элемент:
поле Указатель_на_следующий
Конец описания.
Класс Список должен включать три метода, составляющих итератор: метод Определить_первый, который должен возвращать указатель на первый элемент, метод Определить_следующий, который должен возвращать указатель на следующий элемент, и метод Конец_списка, который должен возвращать «да», если список исчерпан.
Класс Список
реализация
поля Указатель_на_первый, Указатель _на_текущий
интерфейс
метод Добавить_перед_первым (аЭлемент)
метод Удалить_пoследний
метод Определить_первый
метод Определить_следующий
метод Конец_списка
Конец описания.
Тогда поэлементная обработка списка будет программироваться следующим образом:
Элемент:= Определить_первый
цикл-пока не Конец_списка
Обработать элемент, возможно, переопределив его тип
Элемент: = Определить _следующий
все_цикл
При использовании второго способа поэлементной обработки реализуемой структуры процедура обработки элемента передается в списке параметров. Такую процедуру можно определить, если известен тип обработки, например, процедура вывода значений информационных полей объекта. Процедура должна вызываться из метода для каждого элемента данных. В языках с жесткой типизацией данных тип процедуры должен описываться заранее, при этом часто невозможно предусмотреть, какие дополнительные параметры должны передаваться в процедуру. В таких случаях первый способ может оказаться предпочтительнее.
-
Контейнерный класс с процедурой обработки всех объектов (класс Список). В этом случае класс Список будет описываться так:
Класс Список
реализация
поля Указатель_на_первый, Указатель_на_текущий
интерфейс
метод Добавить_перед_первым(аЭлемент)
метод Удалить_последний
метод Выполнить_для_всех(аПроцедура_обработки)
Конец описания.
Соответственно, тип процедуры обработки должен быть описан заранее, с учетом того, что она должна получать через параметры адрес обрабатываемого элемента, например:
Процедура_обработки (аЭлемент)
Использование полиморфных объектов при создании контейнеров позволяет создавать достаточно универсальные классы.
Параметризованные классы. Параметризованный класс (или шаблон) представляет собой определение класса, в котором часть используемых типов компонент класса определяется через параметры. Таким образом, каждый шаблон определяет группу классов, которые, несмотря на различие типов, характеризуются одинаковым поведением. Переопределить тип в процессе выполнения программы нельзя: все операции конкретизации типа выполняются компилятором (точнее - препроцессором).
Параметризованные классы реализованы в C++. Они часто используются для реализации контейнерных классов, причем обычно в качестве элемента выступает полиморфный объект, указатель на базовый класс которого и передается через параметр. Классы, полученные на основе такого шаблона, могут оперировать как с объектами базового класса, так и с объектами производных классов.
-
Шаблон «Список»
-
Шаблон классов (шаблон классов Список). Шаблон (13) практически полностью повторяет описание класса, но везде, где должен указываться тип элемента, вместо него следует указать параметр (в нашем случае Элемент):
-
Шаблон классов Список (Элемент)
реализация
поля Первый, Текущий: ^Элемент
интерфейс
метод Добавить_перед_первым (аЭлемент: ^Элемент)
метод Удалить_последний: ^Элемент
метод Определить_первый: ^Элемент
метод Определить_следующий: ^Элемент
метод Конец_списка
Конец описания.
При использовании шаблона указывают его имя и соответствующее значение параметра, например:
Список (Запись1) или Список (Записъ2)
Реализация контейнеров в виде параметризованных классов по сравнению с обычной реализацией может оказаться более наглядной и, следовательно, предпочтительной.
Исключения. При выполнении любой сложной программы возможно возникновение ситуаций, нарушающих нормальный процесс обработки (например, отсутствует файл данных, делитель выражения равен нулю и т. п.). Такие ситуации принято называть исключительными.
Правильно написанная программа должна предусматривать корректирующие действия в подобных случаях, например, выдавать запрос пользователю и предлагать ему варианты выхода из аварийной ситуации.
В небольших программах такие действия предусмотреть несложно. В больших же программах, в том числе и программах, написанных с использованием ООП, обработка аварийных ситуаций превращается в достаточно сложную задачу, так как нарушения обнаруживаются одним объектом программы, а возможная коррекция должна быть предусмотрена в другом объекте, управляющем данной обработкой.
Для программирования корректирующих действий в таких случаях используют механизм исключений. Этот механизм базируется на том, что обработка некорректных ситуаций выполняется в два этапа:
• генерация информации о возникновении исключительной ситуации - генерация исключения;
• обработка этой информации - перехват исключения.
При перехвате исключений используется стек вызовов, в котором в момент передачи управления очередной подпрограмме фиксируется адрес возврата. Например, если метод A вызывает метод B, а тот в свою очередь вызывает подпрограмму (или метод) C, то в стеке последовательно записываются: адрес возврата в A и адрес возврата в B (Рис. 1.2.).