Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ООП.doc
Скачиваний:
23
Добавлен:
08.11.2018
Размер:
1.4 Mб
Скачать

Инициаторы события

При разработке программы возможна ситуация, когда один и тот же обработчик события будет вызываться разными объектами - инициаторами события. Например, можно сгруппировать обработку щелчков по всем окнам ввода текста на форме в одном методе. Рассмотрим, каким именно образом метод - обработчик события может различить, какой именно объект инициировал событие. Тут могут быть два варианта – если надо различать класс вызвавшего объекта, или если надо различать экземпляры одного класса.

В первом случае используется оператор is, который предназначен для проверки совместимости по присваиванию объекта с классом и возвращает true, если типы совместимы (т.е. объект является экземпляром данного класса или одного из его потомков) и false в ином случае.

Procedure TStatistics(Sender: TObject) begin if (Sender is TButton) then NumberButtonClick := NumberButtonClick+1; else NumberEditClick := NumberEditClick+1; End;

Если же необходимо различать объекты одного класса, то приходится прибегать к дополнительным ухищрениям. Типовой прием – сделать все классы - инициаторы событий потомками одного общего предка и организовать у него свойство, например, Tag. А при создании объекта записывать в это свойство его порядковый номер.

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

Например:

if (Sender is TEdit) (Sender as TEdit).text := “Hello”;

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

Делегирование

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

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

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

Во-вторых, можно назначать разные обработчики событий одному и тому же объекту во время работы программы. Это позволяет управлять логикой поведения программы, изменяя поведение объектов уже во время ее исполнения. Например, при моделировании производственного участка, обычное поведение станка – обрабатывать детали, но станок может изломаться и его поведение изменится.

Методы обработчики событий, реализующие изменяемое поведение объекта, как уже было сказано ранее, должны быть методами какого-либо класса, поскольку типы указатель на процедуру и указатель на метод не совместимы. Понятно, что они не могут быть методами того класса, чьи события обрабатывают, поскольку это свело бы на нет все преимущества. Возможный путь решения – создание специального класса, единственное предназначение объекта которого – быть носителем методов, которые делегируются другим объектам. На рис. 16, такой объект называется «Другой объект».

    1. C++ Указатели на функцию

Возможны только две операции с функциями: вызов и взятие адреса. Указатель, полученный с помощью последней операции, можно впоследствии использовать для вызова функции. Например:

void error(char* p) { /* ... */ } void (*efct)(char*); // указатель на функцию

void f() { efct = &error; // efct настроен на функцию error (*efct)("error"); // вызов error через указатель efct }

Для вызова функции с помощью указателя надо вначале выполнить операцию обратной ссылки, *efct. Поскольку приоритет операции вызова функции () выше, чем у обратной ссылки *, нельзя написать просто *efct("error"). Это будет означать *(efct("error")), что является ошибкой. По той же причине скобки нужны и при описании указателя на функцию. Однако писать просто efct("error") можно, т.к. компилятор понимает, что efct является указателем на функцию, и создает команды, выполняющие вызов нужной функции.

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

void (*pf)(char *); //указатель на void(char *) void f1(char *); //функция void(char *) int f2(char *); //функция int(char *) void f3(int *); //функция void(int *)

void f() { pf = &f1; //правильно //pf = &f2; //ошибка – неверный тип возвращаемого значения //pf = &f3; //ошибка – неверный тип аргумента

(*pf)("asdf");//правильно (*pf)(1); //ошибка – неверный тип аргумента int i = (*pf)("qwer");//ошибка – void присваивается int }

Часто бывает удобнее обозначить тип указателя на функцию именем, чем все время использовать достаточно сложную запись. Например:

typedef int (*FUNC1)(char*);//FUNC1 - тип указатель на функцию FUNC1 f; //переменная типа указатель на функцию void f2(FUNC1 f);//указатель на функцию передается //в качестве аргумента

int i = (*f)("asdf"); //вызов функции через указатель

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

Например:

typedef void (*PF); //тип указателя

PF edit_ops[] = { ... //операции редактирования }; PF file_ops[]= { ... //операции над файлами };

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

Стандарт С++ не обеспечивает удобной работы с указателями на функции-элементы классов. Единственный путь получить указатель на функцию-элемент – это определить для нее полное имя, включающее имя класса. Например:

class CBase { public: void func(int x) { }; };

typedef void (CBase::* pBaseMember)(int);//определяем указатель //на функцию элемент класса CBase, указывая имя класса

int main() { CBase baseObject; //объект pBaseMember m = &CBase::func; //получаем указатель //на функцию элемент func()

(baseObject.*m)(17);//вызываем func() через указатель return 0; }

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

class CBase //базовый класс { public: void func(int x) { }; };

class CDerived: public CBase //производный класс { public: void new_func(int i) { }; };

typedef void (CBase::* pBaseMember)(int);//указатель на функцию //элемент базового класса CBase

int main() { CDerived derivedObject; //объект производного класса //pBaseMember m = &CDerived::new_func; //недопустимо return 0; }

Некоторые компиляторы С++ предоставляют более удобные способы работы с указателями на функции - элементы классов. Но здесь эти возможности не рассматриваются.