
Объектно-ориентированное программирование
.pdf
Страница 41 из 51 |
Основные принципы ООП |
Следует различать статическое и динамическое делегирование. При статическом делегировании соответствующий указатель инициализируется в процессе компиляции программы и при выполнении программы не меняется. При динамическом делегировании значение указателю присваивается в процессе выполнения программы и может изменяться в зависимости от ситуации.
Статическое делегирование используется в двух случаях:
1) если требуемое поведение объекта уже описывалось для объектов другого класса - в этом случае делегирование позволяет избавиться от повторов
впрограмме;
2)если класс объявлен с не полностью определенным поведением объек-тов
(обычно так описываются некоторые библиотечные классы) и его поведение уточняется для конкретных экземпляров объектов.
Динамическое делегирование используется при создании объектов с изменяемым поведением, когда конкретный вариант поведения определяется некоторыми внешними обстоятельствами.
Пример 1.13. Делегирование методов (класс Фигура). В примере 1.3
рассматривалась объектная декомпозиция простейшего текстового редактора,
позволяющего рисовать окружности и квадраты с изменяющимися размером,
цветом и положением. В результате этой декомпозиции были получены три объекта: Монитор, Круг и Квадрат (см. рис. 1.10). Те же действия будет выполнять программа, при разработке которой использован объект с

Страница 42 из 51 |
Основные принципы ООП |
динамическим поведением. Объектная декомпозиция текстового редактора в этом случае будет включать два объекта - Монитор и Фигура (рис. 1.31).
В а р и а н т 1. Класс Фигура описан без указания конкретного варианта выводимой фигуры.
Класс Фигура1: реализация
поля X, Y, R, Color поле
Адрес_метода_рисования интерфейс
конструктор Создать (аХ, aY, aR, aColor,
аАдрес_метода_рисования) метод
Изменитьцвет (aColor) метод Изменить_размер (aR) метод
Изменитьместоположение (аХ, aY) Конец описания.
Адрес конкретного метода рисования должен указываться при создании соответствующего объекта. Изменение этого адреса в процессе выполнения программы не предусмотрено. В данном случае используется статическое делегирование.
В а р и а н т 2. Возможен другой вариант: класс Фигура содержит метод,
который меняет используемую процедуру рисования.
Класс Фигура2 : реализация
поля х, у, г, Color поле Адрес_метода_рисования
интерфейс
конструктор Создать (ах, ay, аг, aColor,
аАдрес_метода_рисования)
Страница 43 из 51 |
Основные принципы ООП |
метод Изменитьцвет (aColor) метод Изменить_размер (aR) метод
Изменитьместоположение (аХ, aY) метод Изменить_тип_Фигуры
(аАдрес_метода_рисования) Конец описания.
Выполнив указанный метод для объектов данного класса, можно изменить процедуру рисования в процессе выполнения программы -динамическое делегирование.
Сами методы рисования могут быть определены как в том же, так и в другом классе, например:
Класс Метод ырисования: интерфейс
метод Рисование_окружности(аХ, aY, aR, aColor) метод
Рисование_квадрата(аХ, aY, aR, aColor) Конецописания.
При определении метода в другом классе, к сожалению, приходится в явном виде передавать все необходимые параметры рисования, так как в противном случае последние будут недоступны. При определении делегируемых методов в своем классе этой проблемы не возникает.
В а р и а н т 3.
Класс ФигураЗ: реализация
поля X, Y, R, Color
поле Адрес_метода_рисования
метод Рисованиеокружности
метод Рисованиеквадрата интерфейс
конструктор Создать(аХ, aY, aR, aColor, aFiguratype) метод
Изменитьцвет (aColor)
метод Изменить_размер (аЯ)
метод Изменить_местоположение (аХ, аУ)
метод Изменить_тип_фигуры (аТип_фигуры)
Конец описания.
Страница 44 из 51 |
Основные принципы ООП |
Параметр аР1§ига_1уре будет получать значение, определяющее подключаемый к объекту метод рисования.
При использовании делегирования необходимо следить, чтобы заголовок метода соответствовал типу указателя, используемого для хранения адреса метода.
Контейнерные классы. Контейнеры - это специальным образом организованные объекты, используемые для хранения объектов других классов.
Для реализации контейнеров разрабатываются специальные контейнерные классы. Контейнерный класс обычно включает набор методов, позволяющих выполнять некоторые операции, как с отдельным объектом, так и с группой объектов. В виде контейнеров, как правило, реализуются сложные структуры данных (различные виды списков, динамических массивов и т.п.). Разработчик наследует от класса-элемента класс, в который добавляются нужные ему информационные поля, и получает требуемую структуру. При необходимости он может наследовать класс и от контейнерного класса, добавляя к нему свои методы (рис. 1.32).
Контейнерный класс обычно включает методы создания, добавления и удаления элементов. Кроме того, он должен обеспечивать поэлементную обработку (например, поиск, сортировку). Все методы программируются через указатели на класс-элемент. Методы добавления и удаления элементов при выполнении операций обычно обращаются к специальным полям класса-
элемента, используемым для создания структуры (например, для односвязного списка - к полю, хранящему адрес следующего элемента).
Методы, реализующие поэлементную обработку, должны работать с полями данных, определенными в классах-потомках класса-элемента.
Поэлементную обработку реализуемой структуры можно реализовать двумя способами. Первый способ - универсальный - заключается в использовании
итераторов, второй - в определении специального метода, который содержит в списке параметров адрес процедуры обработки.

Страница 45 из 51 |
Основные принципы ООП |
Теоретически итератор должен обеспечивать возможность реализации циклических действий следующего вида:
<очередной элемент>:=<первый элемент> цикл-пока
<очередной элемент> определен
<выполнить обработку> <очередной элемент>:=<следующий элемент> все-цикл.
Поэтому обычно он состоит из трех частей: метод, позволяющий организовать обработку данных с начала (получение первого элемента структуры); метод, организующий переход к следующему элементу, и метод,
позволяющий проверить окончание данных. Доступ к очередной порции данных при этом осуществляется через специальный указатель текущей порции данных
(указатель на объект класса-элемента).
Пример 1.14. Контейнерный класс с итератором (класс Список).
Разработаем контейнерный класс Список, реализующий линейный односвязный список из объектов класса Элемент.
Класс Элемент:
поле Указатель_на_следующий Конец
описания.
Класс Список включает три метода, составляющих итератор: метод Определить_первый, который должен возвращать указатель на первый элемент,
метод Определитьследующий, который должен возвращать указатель на следующий элемент, и метод Конецсписка, который должен возвращать « да»,
если список исчерпан.
Страница 46 из 51 |
Основные принципы ООП |
Класс Список реализация
поля Указательнапервый, Указательнатекущий интерфейс метод Добавитьпередпервым (аЭлемент) метод
Удалитьпоследний метод Определить_первый метод
Определить_следующий метод Конец_списка
Конец описания.
Тогда поэлементная обработка списка будет программироваться следующим образом:
Элемент:^ Определить_первый цикл-пока не Конец_списка
Обработать элемент, переопределив его тип Элемент:^ Определитьследующий все-цикл
При использовании второго способа поэлементной обработки реализуемой структуры процедура обработки элемента передается в списке параметров.
Такую процедуру можно определить, если известен тип обработки, например,
процедура вывода значений информационных полей объекта. Процедура должна вызываться из метода для каждого элемента данных. В языках с жесткой типизацией данных тип процедуры должен описываться заранее, при этом часто невозможно предусмотреть, какие дополнительные параметры должны передаваться в процедуру. В таких случаях первый способ может оказаться предпочтительнее.
Пример 1.15. Контейнерный класс с процедурой обработки всех
объектов (класс Список). В этом случае класс Список будет описываться
следующим образом:
Класс Список реализация
поля Указательнапервый, Указатель_на_текущий интерфейс метод Добавить_перед_первым(аЭлемент) метод
Удалитьпоследний
Страница 47 из 51 |
Основные принципы ООП |
метод Выполнить_для_всех(аПроцедура_обработки) Конец
описания.
Соответственно, тип процедуры обработки должен быть описан заранее, с
учетом того, что она должна получать через параметр адрес обрабатываемого элемента, например:
Процедура_обработки (аЭлемент)
Использование полиморфных объектов при создании контейнеров позволяет создавать достаточно универсальные классы.
Параметризованные классы. Параметризованный класс (или шаблон)
представляет собой определение класса, в котором часть используемых типов компонент класса определяется через параметры. Таким образом, каждый шаблон определяет группу классов, которые, несмотря на различие типов,
характеризуются одинаковым поведением. Переопределить тип в процессе выполнения программы нельзя: все операции конкретизации типа выполняются компилятором (точнее - препроцессором) С++.
Параметризованные классы реализованы в С++. Они часто используются для реализации контейнерных классов, причем обычно в качестве элемента выступает полиморфный объект, указатель на базовый класс которого и передается через параметр. Классы, полученные из такого шаблона, могут оперировать как с объектами базового класса, так и с объектами производных классов.
Пример 1.16. Шаблон классов (шаблон классов Список), Шаблон практически полностью повторяет описание класса, но везде, где должен указываться тип элемента, вместо него следует указать параметр (в нашем случае Тип_элемента):
Шаблон классов Список (Типэлемента) реализация
Страница 48 из 51 |
Основные принципы ООП |
поле Указательнапервый: Указатель на Типэлемента поле
Указатель_на_текущий: Указатель на Тип_элемента интерфейс метод
Добавить_перед_первым
(аЭлемент: Указатель на Типэлемента) метод Удалить_последний:
Указатель на Тип_элемента метод Определить_первый: Указатель на
Тип_элемента метод Определить_следующий: Указатель на Тип_элемента метод Конецсписка Конец описания.
При использовании шаблона указывается его имя и соответствующее значение параметра, например:
Список (Запись1)
или
Список (Запись2)
Реализация контейнеров в виде параметризованных классов по сравнению с обычной реализацией может оказаться более наглядной и, следовательно,
предпочтительной.
Исключения. При выполнении любой сложной программы возможно возникновение ситуаций, нарушающих нормальный процесс обработки
(например, отсутствует файл данных, делитель выражения равен нулю и т.п.).
Такие ситуации принято называть исключительными.
Правильно написанная программа должна предусматривать корректирующие действия в подобных случаях, например, выдавать соответствующую информацию пользователю и предлагать ему варианты выхода из аварийной ситуации.
В небольших программах такие действия предусмотреть несложно. В
больших же программах, в том числе и программах, написанных с использованием ООП, обработка аварийных ситуаций превращается в достаточно сложную задачу, так как нарушения обнаруживаются в одном месте программы (объектом, выполняющим обработку), а возможная коррекция должна быть предусмотрена в другом (в объекте, инициирующем данную обработку).
Страница 49 из 51 |
Основные принципы ООП |
Для программирования корректирующих действий в таких случаях используют механизм исключений. Этот механизм базируется на том, что обработка некорректных ситуаций выполняется в два этапа:
генерация информации о возникновении исключительной ситуации -
генерация исключения;
обработка этой информации - перехват исключения.
При перехвате исключений используется стек вызовов, в котором в момент передачи управления очередной подпрограмме фиксируется адрес возврата.
Например, если подпрограмма (или метод) А вызывает подпрограмму (или метод) В, а та в свою очередь вызывает подпрограмму (или метод) С, то в стеке последовательно записываются: адрес возврата в А и адрес возврата в В (рис. 1.33).
Ошибка, обнаруженная в подпрограмме С, может быть обработана в самой подпрограмме С, а может быть передана для обработки подпрограммой В или подпрограммой А, или даже подпрограммами, вызвавшими А. Для этого в одной из этих подпрограмм необходимо предусмотреть обработчик исключительных ситуаций, соответствующий по типу обнаруженной ошибке.
Поиск нужного обработчика осуществляется следующим образом. Вначале проверяется, не описан ли требуемый обработчик в подпрограмме С. Если обработчик отсутствует, то по стеку вызовов определяется подпрограмма,
вызвавшая подпрограмму С, и проверяется наличие обработчика в ней. Не обнаружив обработчика в подпрограмме В, система по стеку определит подпрограмму, вызвавшую В, и продолжит поиск обработчика в ней. Обратный просмотр стека будет продолжаться до тех пор, пока не обнаружится обработчик требуемого типа или не выяснится, что такой обработчик в программе не определен. В последнем случае программа завершается аварийно.

Страница 50 из 51 |
Основные принципы ООП |
Механизм исключений предполагает использование двух конструкций.
Первая - обеспечивает описанный выше алгоритм поиска обработчика
исключения. Она может быть названа обрабатывающей конструкцией
(try...except - в Delphi Pascal, try... catch - в стандарте С++, try ... ex-
cept - в стандарте С). Операторы обработки исключения выполняются только,
если при выполнении заданного фрагмента программы обнаруживается исключение указанного типа.
Вторая - используется в тех случаях, когда необходимо обеспечить правильное освобождение ресурсов метода даже при обнаружении
исключительных ситуаций. Она называется завершающей конструкцией обработки исключения (try... finally - в Delphi Pascal, try... finally - в
стандарте С, try... finally - в стандарте C++Builder). Завершающая обработка
исключения отличается от обычной тем, что блок операторов завершающей обработки выполняется в любом случае: как при обнаружении исключения, так и при его отсутствии. Эта конструкция обычно используется для обеспечения освобождения памяти, закрытия файлов и т.д.
В обоих случаях в месте обнаружения исключительной ситуации программист организует генерацию исключения (raise - в Delphi Pascal, throw - в
С++, вызывает функцию RaiseException - в С). При генерации исключения создается некоторый объект, содержащий информацию об обнаруженной ситуации. В простейшем случае таким объектом может служить скалярная переменная одного из стандартных типов, а в более сложных - объект описанного ранее класса. В качестве информации может использоваться номер