Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП_Лекции 2010.doc
Скачиваний:
69
Добавлен:
17.03.2015
Размер:
954.37 Кб
Скачать
    1. Дополнительные средства и приемы разработки классов

Рассмотренные в § 7.2 средства разработки классов являются основными. Они предусматриваются принципами ООП. Однако к настоящему моменту постепенно складывается более сложная модель ООП, включающая такие понятия, как метаклассы, контейнерные классы, делегирование методов, параметризованные классы. Появление в языках программирования соответствующих средств позволяет создавать более эффективные программы.

Метаклассы. Дальнейшее развитие идеи реализации полиморфных объектов привело к появлению более высокого уровня абстракции - метаклассов. Метакласс - тип, значениями которого являются классы как ссылки на типы. Переменные типа метакласса можно использовать вместо явного указания класса в соответствии с традиционными правилами организации косвенного доступа (9).

  1. Прямое и косвенное обращения к классу

Реализация метаклассов базируется на использовании специальных таблиц, в которых хранится информация о классе: имя класса, имя класса-родителя, адреса методов и т. д. Поскольку эти таблицы используются во время выполнения программы, они получили название RTTI (Run Time Type Information - «информация о типе времени выполнения»).

Эти же таблицы используются для реализации следующих операций:

• операции проверки принадлежности объекта заданному классу или его потомкам;

• операции уточнения класса объекта, которая отличается от операции явного переопределения типа тем, что перед переопределением выполняется проверка принадлежности объекта данному классу или его потомкам;

• специальных операций определения, модификации или проверки свойств класса (как типа) - для реализации этих операций язык программирования должен предоставлять возможность описания полей и методов класса, обращение к которым возможно при отсутствии объектов класса.

Делегирование методов. Делегирование - «заимствование» методов у объектов других классов. Оно представляет собой альтернативу переопределению методов, используемому полиморфными объектами. В отличие от переопределения, делегирование позволяет определять различное поведение объектов, принадлежащих одному классу. Причем заимствование методов возможно как в пределах класса или иерархии классов, так и у объектов классов других иерархий.

Метод при этом вызывается косвенно, через указатель на него. Язык, в котором реализуется делегирование, должен обеспечивать возможность определения указателей на методы. Назначение или замена метода осуществляется присваиванием соответственного значения соответствующему указателю на метод (10).

  1. Делегирование метода

Целесообразно различать статическое и динамическое делегирование. При статическом делегировании соответствующий указатель инициализируется в процессе компиляции программы и при выполнении программы не меняется. При динамическом делегировании значение указателю присваивается в процессе выполнения программы и может изменяться в зависимости от ситуации.

Статическое делегирование используют в двух случаях:

• если требуемое поведение объекта уже описывалось для объектов другого класса - в этом случае делегирование позволяет избавиться от повторов в программе;

• если класс объявлен с не полностью определенным поведением объектов (часто так описываются библиотечные классы) и его поведение уточняется для конкретных экземпляров объектов.

Примечание. Язык C++ в первом случае позволяет использовать множественное наследование, но оно может оказаться неэффективным, поскольку вместе с желаемым поведением придется наследовать и все остальные элементы класса.

Динамическое делегирование используется при создании объектов с изменяемым поведением, когда конкретный вариант поведения определяется некоторыми внешними обстоятельствами.

Пример 1.15. Делегирование методов (класс Фигура). В примере 1.1Пример 4.1 рассматривалась объектная декомпозиция простейшего графического редактора, позволяющего рисовать окружности и квадраты с изменяющимся размером, цветом и положением. В результате этой декомпозиции были получены три объекта: Монитор, Круг, и Квадрат (см.1Рис. 1.5.). Те же действия будет выполнять программа, при разработке которой использован объект с динамическим поведением. Объектная декомпозиция графического редактора в этом случае будет включать два объекта - Монитор и Фигура (11).

  1. Объектная декомпозиция графического редактора

Вариант 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).

  1. Построение классов на базе контейнерного класса и класса элемента

Контейнерный класс обычно включает методы создания, добавления и удаления элементов. Кроме того, он должен обеспечивать поэлементную обработку (например, поиск, сортировку). Все методы программируются для объектов класса-элемента. Методы добавления и удаления элементов при выполнении операций часто обращаются к специальным полям класса-элемента, используемым для создания структуры (например, для односвязного списка - к полю, хранящему адрес следующего элемента).

Методы, реализующие поэлементную обработку, должны работать с полями данных, определенными в классах-потомках класса-элемента.

Поэлементную обработку реализуемой структуры можно осуществлять двумя способами. Первый способ - универсальный - заключается в использовании итераторов, второй - в определении специального метода, который содержит в списке параметров адрес процедуры обработки.

Теоретически итератор должен обеспечивать возможность реализации циклических действий следующего вида:

<очередной элемент>:=<первый элемент>

цикл-пока <очередной элемент> определен

<выполнить обработку>

<очередной элемент>:=<следующий элемент>

все-цикл

Поэтому обычно он состоит из трех частей: метод, позволяющий организовать обработку данных с первого элемента (получение адреса первого элемента структуры); метод, организующий переход к следующему элементу, и метод, позволяющий проверить окончание данных. Доступ к очередной порции данных при этом осуществляют через специальный указатель текущей порции данных (указатель на объект класса-элемента).

        1. Контейнерный класс с итератором (класс Список). Разработаем контейнерный класс Список, реализующий линейный односвязный список из объектов класса Элемент, описанных следующим образом:

Класс Элемент:

поле Указатель_на_следующий

Конец описания.

Класс Список должен включать три метода, составляющих итератор: метод Определить_первый, который должен возвращать указатель на первый элемент, метод Определить_следующий, который должен возвращать указатель на следующий элемент, и метод Конец_списка, который должен возвращать «да», если список исчерпан.

Класс Список

реализация

поля Указатель_на_первый, Указатель _на_текущий

интерфейс

метод Добавить_перед_первым (аЭлемент)

метод Удалить_пoследний

метод Определить_первый

метод Определить_следующий

метод Конец_списка

Конец описания.

Тогда поэлементная обработка списка будет программироваться следующим образом:

Элемент:= Определить_первый

цикл-пока не Конец_списка

Обработать элемент, возможно, переопределив его тип

Элемент: = Определить _следующий

все_цикл

При использовании второго способа поэлементной обработки реализуемой структуры процедура обработки элемента передается в списке параметров. Такую процедуру можно определить, если известен тип обработки, например, процедура вывода значений информационных полей объекта. Процедура должна вызываться из метода для каждого элемента данных. В языках с жесткой типизацией данных тип процедуры должен описываться заранее, при этом часто невозможно предусмотреть, какие дополнительные параметры должны передаваться в процедуру. В таких случаях первый способ может оказаться предпочтительнее.

        1. Контейнерный класс с процедурой обработки всех объектов (класс Список). В этом случае класс Список будет описываться так:

Класс Список

реализация

поля Указатель_на_первый, Указатель_на_текущий

интерфейс

метод Добавить_перед_первым(аЭлемент)

метод Удалить_последний

метод Выполнить_для_всех(аПроцедура_обработки)

Конец описания.

Соответственно, тип процедуры обработки должен быть описан заранее, с учетом того, что она должна получать через параметры адрес обрабатываемого элемента, например:

Процедура_обработки (аЭлемент)

Использование полиморфных объектов при создании контейнеров позволяет создавать достаточно универсальные классы.

Параметризованные классы. Параметризованный класс (или шаблон) представляет собой определение класса, в котором часть используемых типов компонент класса определяется через параметры. Таким образом, каждый шаблон определяет группу классов, которые, несмотря на различие типов, характеризуются одинаковым поведением. Переопределить тип в процессе выполнения программы нельзя: все операции конкретизации типа выполняются компилятором (точнее - препроцессором).

Параметризованные классы реализованы в C++. Они часто используются для реализации контейнерных классов, причем обычно в качестве элемента выступает полиморфный объект, указатель на базовый класс которого и передается через параметр. Классы, полученные на основе такого шаблона, могут оперировать как с объектами базового класса, так и с объектами производных классов.

  1. Шаблон «Список»

        1. Шаблон классов (шаблон классов Список). Шаблон (13) практически полностью повторяет описание класса, но везде, где должен указываться тип элемента, вместо него следует указать параметр (в нашем случае Элемент):

Шаблон классов Список (Элемент)

реализация

поля Первый, Текущий: ^Элемент

интерфейс

метод Добавить_перед_первым (аЭлемент: ^Элемент)

метод Удалить_последний: ^Элемент

метод Определить_первый: ^Элемент

метод Определить_следующий: ^Элемент

метод Конец_списка

Конец описания.

При использовании шаблона указывают его имя и соответствующее значение параметра, например:

Список (Запись1) или Список (Записъ2)

Реализация контейнеров в виде параметризованных классов по сравнению с обычной реализацией может оказаться более наглядной и, следовательно, предпочтительной.

Исключения. При выполнении любой сложной программы возможно возникновение ситуаций, нарушающих нормальный процесс обработки (например, отсутствует файл данных, делитель выражения равен нулю и т. п.). Такие ситуации принято называть исключительными.

Правильно написанная программа должна предусматривать корректирующие действия в подобных случаях, например, выдавать запрос пользователю и предлагать ему варианты выхода из аварийной ситуации.

В небольших программах такие действия предусмотреть несложно. В больших же программах, в том числе и программах, написанных с использованием ООП, обработка аварийных ситуаций превращается в достаточно сложную задачу, так как нарушения обнаруживаются одним объектом программы, а возможная коррекция должна быть предусмотрена в другом объекте, управляющем данной обработкой.

Для программирования корректирующих действий в таких случаях используют механизм исключений. Этот механизм базируется на том, что обработка некорректных ситуаций выполняется в два этапа:

• генерация информации о возникновении исключительной ситуации - генерация исключения;

• обработка этой информации - перехват исключения.

При перехвате исключений используется стек вызовов, в котором в момент передачи управления очередной подпрограмме фиксируется адрес возврата. Например, если метод A вызывает метод B, а тот в свою очередь вызывает подпрограмму (или метод) C, то в стеке последовательно записываются: адрес возврата в A и адрес возврата в B (Рис. 1.2.).