11.5. Взаимодействие объектов в программе

    11.5. «Тотальное» ООП Инженер знает, как разработать прибор, но не знает, кому продать и где взять на это деньги. Финансовый директор знает, где взять деньги, но не знает, что с ними делать. Коммерческий директор знает, кому продать прибор, но не знает, как его сделать. Менеджер знает, как организовать работу инженера, но не знает, чем инженеру следует заниматься. И, наконец, генеральный директор не знает ничего перечисленного, но может заставить работать всех вместе.

    Приведенный образец «разделения труда» как нельзя лучше отражает основной принцип ООП: каждый класс моделирует отдельную сущность и работает в пределах своей компетенции. С точки зрения этого подхода программа выглядит как система взаимодействующих объектов различных классов. При этом основной элемент традиционной технологии программирования – поток управления (последовательность команд) как бы «исчезает». На самом деле он никуда не девается, просто его становится сложно отследить в тексте программы, которая, как единое синтаксическое целое, «распадается» на систему объектов. Перечислим еще раз компоненты технологии, «ответственные» за такое представление программы.

    Синтаксическая последовательность «объект-метод-объект». Объектно-ориентированный подход заключается в первичности данных (объектов) по отношению к алгоритмам (методам) их обработки. Причем любая сущность, с которой программист сталкивается при написании программы, должна являться объектом (меню, файл, таблица, строка, вплоть до самой main). Цепочка вызовов функций («функция-функция») в такой программе заменяется на последовательность «объект-метод-объект», которая уже не всегда является строго древовидной. Рассмотрим два примера.

    Рис. 115-1. Цепочка «объект-метод-объект»

    Объект aa класса A вызывает метод F, в котором создается локальный объект bb класса B, для которого вызывается метод G. Здесь мы имеем некоторый эквивалент цепочки вызовов функций: объект во время работы порождает объект, во время работы с ним порождается объект и т.д..

    class B{

    public: void G(){ … }

    };

    class A{

    public: void F(){ B bb; bb.G(); … }

    };

    void main(){ A aa; aa.F(); }

    Структура данных, хранящая указатели на объекты различных типов с общим (интерфейсным) базовым классом, сохраняет свое содержимое в двоичном файле. Определяется локальный объект класса BinFile (двоичный файл). Затем метод просматривает структуру данных, выбирая из нее указатели на объекты, и вызывает для них виртуальную функцию Append, которая в качестве параметра получает ссылку на двоичный файл. В результате будет выполнен метод Append в одном из производных классов, который в свою очередь может использовать объект BinFile для выполнения собственных действий, определяемых форматом представления этого объекта в двоичном файле.

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

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

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

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

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

    • TEXT
         порождение объектами объектов. Объект класса, создающий динамический объект, несет полную ответственность за работу с ним и доступ к нему. Обычно это осуществляется в форме сеанса: объект создает динамический объект и запоминает указатель на него, после чего он может работать с этим объектом сам, передавать указатель другим объектам, а также уничтожать его по окончании работы. Если динамических объектов несколько, то это не меняет дела: объект – прародитель должен интегрировать указатели на них в собственную структуру данных;
    • TEXT
         интегрирование динамических объектов в структуру данных. Объекты  нескольких родственных классов (например, графические элементы изображения) могут интегрироваться в общую структуру данных, через которую любой желающий  может получить доступ к ним. Этот механизм можно скрыть в базовом классе, тогда интеграция будет неявной и запоминание указателя на объект не потребуется вообще.
    • TEXT
         скрытые механизмы  взаимодействия объектов. До сих пор считалось, что для доступа к объекту нужно знать его имя, либо иметь указатель на него. В принципе, указатели на объекты можно сохранять средствами базового класса, включив в него скрытые механизмы взаимодействия объектов. Например, это может быть сделано в виде системы обмена сообщениями по принципу широковещательной локальной сети - «каждый со всеми». Объект имеет право послать сообщение, которое будет получено всеми объектами программы. В этом случае «правила игры» устанавливаются на основе реакции различных объектов на сообщения различных типов. 

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

    • TEXT
         класс сообщений;
    • TEXT
         базовый класс «процессов», управляемых сообщениями;
    • TEXT
         производные классы «процессов», объекты которых взаимодействуют друг с другом на основе обмена сообщениями различных типов;
    • TEXT
         класс прикладной программы, который содержит в себе структуры данных системы управления  и реализует в своем методе run диспетчерские функции распределения сообщений по объектам-«процессам».

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

    C
    
    //------------------------------------------------------------------------------------------------115-01.cpp
    
    // Система объектов, управляемых сообщениями
    
    //--------------Шаблон класса очереди, содержащей адреса объектов
    
    template <class T> class queue{                        
    
    public:   struct elem{                   // Элемент списка
    
                T *data;                         // Указатель на хранимый объект
    
                elem *next;                    // Ссылка в списке
    
                };
    
    elem *fst,*lst;                             // Указатели на первый и последний в списке
    
    queue();
    
    ~queue();
    
    T *out();                         // Извлечение из очереди
    
    void in(T *s);                              // Постановка в очередь
    
    void remove(T *s); };                    // Удаление по адресу хранимого объекта
    

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

    //------------------------------------------------------------------------------------------------115-01.cpp

    struct mes {

    TEXT
            int         type;                             // Тип сообщения
    
            long      value;                            // Значение сообщения
    
            void       *addr;                            // Адрес объекта
    
            mes(int t0, long v0=0, void *a=NULL);
    
            };

    Базовый класс process является основой всех объектов-процессов, при конструировании он запоминает указатель на объект-«программу» как непосредственно, так и из объекта-прародителя. Кроме того, в нем содержится метод передачи сообщения и виртуальный метод (функция) обработки сообщения procMes, которая переопределяется в производных классах.

    //------------------------------------------------------------------------------------------------115-01.cpp

    class process { // Класс "процесс" - указатель объекта "программа"

    TEXT
            programm0 *prg;

    public: // Конструктор: процесс порождается программой

    TEXT
            process(programm0 *p0=NULL){
    
                        prg=p0;                                    
    
                        send(wasBorn,0,this);}    // Конструктор: процесс порождается процессом
    
            process(process *parent){
    
                        prg=parent->prg;
    
                        send(wasBorn,0,this);}
    
            void send(int t0,long v0=0,void *a0=NULL)

    { prg->M.in(new mes(t0,v0,a0)); }

    TEXT
                                                            // Виртуальная функция обработки сообщения
    
            virtual int procMes(mes &m){ return 0; }
    
            virtual ~process(){}
    
            };

    Класс programm содержит два объекта шаблонного класса queue – очереди сообщений и очередь объектов-процессов. Ввиду того, что классы program и process взаимно ссылаются друг на друга, класс program разбит на два (базовый и производный), в связи с чем они определяются в последовательности programm0 – process – program.

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

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

    #define wasBorn 0 // Сообщение - создать процесс

    #define killMe 1 // Сообщение - уничтожить процесс

    #define keyBrd 2 // Сообщение от клавиатуры

    //------------------------------------------------------------------------------------------------115-01.cpp

    class programm0{ // Начало класса "программа" - очередь сообщений

    public:

    TEXT
            queue<mes> M;
    
            };

    //------------------------------------------------------------------------------------------------115-01.cpp

    class programm : public programm0{

    TEXT
            queue<process> P;                    // Класс "программа" - очередь процессов

    public:

    TEXT
            void run(){                                  // Диспетчер сообщений
    
                        while(1){                        // Сообщения от источника - клавиатуры
    
                        M.in(new mes(keyBrd,getch()));
    
                        while(M.isData()){           // Очередь сообщений не пуста
    
                                    mes *mm=M.out();// Извлечь сообщение          
    
                                                            // Сообщение о порождении процесса
    
                                                            // поставить в очередь
    
                                    if (mm->type==wasBorn)P.in((process*)mm->addr);
    
                                    else
    
                                    if (mm->type==killMe) {
    
                                                            // Сообщение об уничтожении процесса
    
                                                            // удалить из очереди и уничтожить объект
    
                                                process *q=(process*)mm->addr;
    
                                                P.remove(q);
    
                                                delete q;
    
                                                }
    
                                    else                  // Иначе - пропустить через все процессы
    
                                    for (queue<process>::elem *q=P.fst; q!=NULL; q=q->next)
    
                                                if (q->data->procMes(*mm)) break;
    
                                    delete mm;
    
                                    }}}};

    Простейшая система процессов содержит объекты-процессы двух видов. Первичный процесс класса PRC1 при получении сообщения от клавиатуры с символом «+» создает вторичный объект-процесс класса PRC2. Он, в свою очередь по сообщению с символом «?» выводит собственный адрес, а по сообщению с символом «-» посылает объекту-«программе» сообщение с просьбой уничтожить его. При этом он запрещает дальнейшее распространение сообщения, в связи с чем каждый раз уничтожается только один процесс.

    //------------------------------------------------------------------------------------------------115-01.cpp

    // Вторичный процесс. При получении '-' просит уничтожить себя,

    // При получении '?' - сообщает о себе

    class PRC2 : process{

    public: PRC2(process *pp):process(pp){}

    TEXT
            int procMes(mes &m){               
    
                        if (m.type==keyBrd){
    
                                    if  (m.value=='-') { send(killMe,0,this); return 1; }
    
                                    if  (m.value=='?') { printf("A am %lx\n",this); }
    
                                    }
    
                        return 0; }};

    // Первичный процесс. При получении '+' порождает вторичный процесс

    class PRC1 : process{

    public: PRC1(programm *pp):process(pp){}

    TEXT
            int procMes(mes &m){
    
                        if (m.type==keyBrd){
    
                                    if  (m.value=='+') new PRC2(this);
    
                                    }
    
                        return 0;}};

    И наконец, в main определяется объект-«программа» и с ним связывается динамический объект-процесс PRC1, после чего в «программе» вызывается метод run – диспетчер сообщений.

    void main() { // Основная программа

    TEXT
            programm P;     // Определяет объект «программа»
    
            new PRC1(&P); // Создает «процесс» и привязывает его к «программе»
    
            P.run();             // Вызывает метод run в «программе»
    
            }

    Событийное программирование. Модель оконного приложения в Java Программирование на основе обмена сообщениями между компонентами (объектами) называют еще событийным. Наиболее известным примером из этой области являются оконные приложения. В отличие от предыдущего примера источником событий является единственный объект – окно, который получает от нижележащего уровня программирования приложения (API), который, в свою очередь, получает их от ядра ОС, включающего себя компоненты графического интерфейса (GUI). В качестве примера воспроизведем механизмы программирования оконных приложений, принятые в Java.

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

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

    //---------------------------------------------------------------------------------115-02.cpp

    class Component{

    public:

    TEXT
            int x,y,w,h;
    
            Component(){
    
                        x=y=h=w=0;
    
                        ml=NULL;
    
                        al=NULL;                                  // Обработчиков нет
    
                        }
    
            void setSize(int x0,int y0,int w0,int h0){   // Установка положения и размеров
    
                        x=x0; y=y0; h=h0; w=w0;
    
                        }
    
            virtual int inside(int xx,int yy){                 // Точка внутри элемента управления
    
                        return xx>=x && xx<=x+w && yy>=y && yy<=y+h;
    
                        }
    
            virtual void paint()=0;      // Перерисовка объекта управления (в ПК)
    
            MouseListener *ml;       // Объект-обработчик события типа "мышь"(в нем метод)
    
            ActionListener *al;         // Объект-обработчик события типа "действие"(в нем метод)
    
            virtual void MouseClick(int xx,int yy)=0;  // Реакция на «клик мышью» по объекту (в ПК)
    
            };

    Объект оконного класса Frame имеет внутреннюю структуру данных, содержащую ссылки (указатели) на созданные объекты управления, а также метод add, с помощью которого они «объявляются» в окне. Таким образом, оконному объекты становятся известны все элементы управления. Они создаются в конструктуоре производного класса и там же передаются в базовый.

    //---------------------------------------------------------------------------------115-02.cpp

    class Frame{

    TEXT
            Component **ctrl;                      // Объекты управления в окне - ДМУ
    
            int nc,sz;

    public:

    TEXT
            Frame(){
    
                        setlocale(LC_ALL,"Russian");
    
                        ctrl=new Component*[20];
    
                        nc=0;
    
                        sz=20;
    
            }
    
            void add(Component *p){            // Сохранить указатель в ДМУ        
    
                        if (nc==sz){ sz*=2; ctrl=(Component**)realloc(ctrl,sz*sizeof(Component*)); }
    
                        ctrl[nc++]=p;
    
            }
    
            virtual void paint(){
    
            // Заливка экрана + прорисовка элементов управления
    
            // ГРАФИКА - отвечает USER
    
                        puts("Перерисовка окна");
    
                        for (int i=0;i<nc;i++) ctrl[i]->paint();
    
            }
    
            // СОБЫТИЯ от ядра ОС - получение событий от ядра
    
            void loop(){
    
                        while(1){
    
                        char c=getch();
    
                        if (c==' ') return;
    
                        if (c=='c') paint();           // Перерисовка экрана == ВЫЗОВ в ПК
    
                        if (c=='m') {                   // МЫШЬ
    
                                   printf("Координаты мыши:");
    
                                   int xx,yy;
    
                                   scanf("%d%d",&xx,&yy);           // Передать объектам !!!!!!!!!!!!
    
                                   for (int i=0;i<nc;i++){
    
                                               if (ctrl[i]->inside(xx,yy)) {
    
                                                           ctrl[i]->MouseClick(xx,yy);
    
                                                           }
    
                                               }
    
                                   }
    
                        }}};

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

    //---------------------------------------------------------------------------------115-02.cpp

    class MyFrame : public Frame,public ActionListener{

    public:

    TEXT
            //---------------------------------------------------------------------------------
    
            // Отложенное программирование - перерисовка графики
    
            void paint(){
    
                        Frame::paint();                          // Явно вызвать старый код
    
                        puts("Перерисовка графики");
    
                        }
    
            MyFrame(){
    
                        Button *b1=new Button("Дави");            // Создать КНОПКУ
    
                        b1->setSize(100,200,50,20);                  //
    
                        add(b1);                                               // ПЕРЕДАТЬ ОКНУ
    
                        b1=new Button("Стоп");                         // Создать КНОПКУ
    
                        b1->setSize(100,230,50,20);
    
                        add(b1);                                               // ПЕРЕДАТЬ ОКНУ
    
                        b1->addActionListener(this);
    
                        b1->addActionListener(new adapter_F2(this));
    
                        TextField *t1=new TextField(10);            // Создать ТЕКСТ
    
                        t1->setSize(100,250,100,20);
    
                        add(t1);                                                // ПЕРЕДАТЬ ОКНУ
    
                        }

    };

    Рассмотрим, как выглядит процесс программирования реакции на события, связанные с перерисовкой окна и кликом мыши по отдельным элементам управления. В базовом классе постоянно «крутится» метод loop (для чего может создаваться отдельный поток, параллелльный main’у). При получании от ядра сообщений об изменении размеров или состояния окна производится перерисовка его содержимого в следующей последовательности:

    • TEXT
         в базовом класссе окна (Frame) вызывается метод paint, поскольку он полиморфный, то происходит переход в производный класс (MyFrame), где он отвечает за рисование графики;
    • TEXT
         в производном классе метод paint первым делом вызывает одноименный метод базового класса, который перезаливает окно, перебирает все заявленные элементы кправления и вызывает (полиморфно) одноименный метод их прорисовки;
    • TEXT
         по возвращении в производный класс в нем производится прорисовка графики.

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

    Создание «обратной связи» от сообщения в базовом классе к обработчику события в окне внешне выглядит просто: необходимо на каждое событие в элементе управления «навесить» функцию, выполняемую к контексте окна (т.е. имеющую доступ к его данным и методам). Т.е. создается цепочка: базовый класс окна – структура данных элементов управления – объект элемента управления – связанная с ним функция, работающая в контексте окна.

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

    Поскольку имеется довольно много видов событий от разных источников, они разбиты на классы. Например, такие действия, как нажатие кнопок и выбор пунктов меню сопровождаются событиями класса ActionEvent (событие типа «действие»). Кроме того, на каждый класс событий имеется абстрактный класс – обработчик событий этого вида (или слушатель). Он не имеет данных и содержит объявление пустых полиморфных методов (в терминах Java это называется интерфейсом). Каждый метод предназначен для обработки своего подвида события. Например, для события типа «действие» имеется интерфейс ActionListener. Он содержит единственный метод – actionPerformed. Интерфейс событий, получаемых от мыши, содержит множество методов по числу производимых мышью действий (наведение, клик, двойной клик и т.д.).

    //---------------------------------------------------------------------------------115-02.cpp

    // Класс СОБЫТИЯ и интерфейс СЛУШАТЕЛЯ для "мыши"

    class MouseEvent{

    public: int x,y;

    TEXT
            MouseEvent(int x0,int y0){ x=x0; y=y0; }
    
            };

    class MouseListener { // КЛАСС (ИНТЕРФЕЙС) CЛУШАТЕЛЯ (Базовый и абстрактный)

    public:

    TEXT
            virtual void mouseClicked(MouseEvent ee)=0;
    
            };

    //--------------------------------------------------------------------------

    // Класс СОБЫТИЯ и интерфейс СЛУШАТЕЛЯ для "действия"

    class ActionEvent{ // МЕНЮ и КНОПКИ - Action

    public:

    TEXT
            char *name;
    
            ActionEvent(char *ss){ name=ss; }
    
            };

    class ActionListener { // КЛАСС (ИНТЕРФЕЙС) CЛУШАТЕЛЯ (Базовый и абстрактный)

    public:

    TEXT
            virtual void actionPerformed(ActionEvent ee)=0;
    
            };

    Базовый класс элементов управления (Component) содержит указатели на объекты базовых классов (интерфейсов) обработчиков событий.

    //---------------------------------------------------------------------------------115-02.cpp

    class Component{

    public:

    TEXT
            …
    
            MouseListener *ml;       // Объект-обработчик события типа "мышь"(в нем метод)
    
            ActionListener *al;         // Объект-обработчик события типа "действие"(в нем метод)
    
            … };

    Производные классы элементов управления содержат методы «назначения» объектов-слушателей, которые сохраняют переданные указатели на обработчики событий, свойственные этому типу объектов.

    //---------------------------------------------------------------------------------115-02.cpp

    class Button : public Component{

    TEXT
            char *label;                               // Надпись на кнопке

    public:

    TEXT
            Button(char *s){ label=s; }          // Запоминание указателя на обработчик события
    
            void addActionListener(ActionListener *ff){ al=ff; }
    
            void addMouseListener(MouseListener *ff){ ml=ff; }
    
            void MouseClick(int xx,int yy){    // Обработка клика мыши по элементу на экране
    
                        if (al!=NULL) al->actionPerformed(ActionEvent(label));
    
                        else if (ml!=NULL) ml->mouseClicked(MouseEvent(xx,yy));
    
                        }                                  // Action на кнопке, если нет обработчика - клик
    
            };
    
           

    Итак, последовательность обработки сообщения, полученного оконным классом, выглядит так:

    • TEXT
         метод loop в базовом классе, получающий в цикле сообщения от ядра ОС, для сообщений, относящихся, к манипуляциями мышью, просматривает все объекты управления (из массива указателей ctrl) и вызвает метод проверки «попадания» клика на объект;
    • TEXT
         при «попадании» вызвается полиморфный метод MouseClick, который в каждом типе объектов управления вызывает соответствуюшие ему обработчики. Например, при клике мышью по кнопке вызвается обработчик типа ActionListener (точнее, метод actionPerformed с передачей надписи на кнопке), а при его отсутствии – обработчик типа MouseListener (метод mouseClicked) с передачей координа мыши;
    • TEXT
         вызов обрабочиков событий является полиморфным, т.к. метод вызывается по ссылке на абстракный базовый класс-слушатель.

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

    • TEXT
         обработчиком является сам оконный класс. В этом случае он наследует интерфейс слушателя и переопределяет методы обработки события. Но если таким образом назначить обработку одного вида событий от нескольких элементов управления, то они будут перенаправляться в единственный метод и в нем придется делать дополнительную селекцию (например кнопки по ее подписи или пункта меню по параметру-строке, передачаемом в объекте-событии);

    //---------------------------------------------------------------------------------115-02.cpp

    class MyFrame : public Frame,public ActionListener{

    public:

    TEXT
      MyFrame(){
    
            Button *b1=new Button("Дави");     // Создать КНОПКУ
    
            b1->setSize(100,200,50,20);
    
            add(b1);                           // ПЕРЕДАТЬ ОКНУ
    
            b1->addActionListener(this);       // Обработчик - само окно
    
            b1=new Button("Стоп");             // Создать КНОПКУ
    
            b1->setSize(100,230,50,20);       
    
            add(b1);                           // ПЕРЕДАТЬ ОКНУ
    
            b1->addActionListener(this);       // Обработчик - само окно
    
            TextField *t1=new TextField(10);   // Создать ТЕКСТ
    
            t1->setSize(100,250,100,20);
    
            add(t1);                           // ПЕРЕДАТЬ ОКНУ
    
            }

    //================================================================

    // Объект-обработчик = само окно - ОБРАТЫВАЕТ СОБЫТИЯ ОТ ВСЕХ ИСТОЧНИКОВ

    TEXT
      void actionPerformed(ActionEvent ee){
    
            printf("Обработка в окне нажатия %s\n",ee.name);
    
      if (strcmp(ee.name, "Стоп")==0) {…}      // Выбор кнопки по подписи
    
      if (strcmp(ee.name, "Дави")==0) {…}
    
      }

    };

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

    //---------------------------------------------------------------------------------115-02.cpp

    class MyFrame : public Frame{

    public: //--------------------------------------------- Вложенный класс-адаптер

    TEXT
            class adapter_F1 : public ActionListener{
    
            MyFrame *pp;                           // Указатель на ОКНО, в котором работает

    public: adapter_F1(MyFrame *p0)

    { pp=p0; } // При создании - запоминает указатель на окно

    TEXT
            void actionPerformed(ActionEvent ee){ pp->F1(ee); }
    
            // При вызове в качестве обработчика событий – вызывает F1 в классе окна
    
            };
    
            class adapter_F2 : public ActionListener{
    
            MyFrame *pp;              

    public: adapter_F2(MyFrame *p0){ pp=p0; }

    TEXT
            void actionPerformed(ActionEvent ee){ pp->F2(ee); }
    
            };
    
            MyFrame(){
    
                        Button *b1=new Button("Дави");            // Создать КНОПКУ
    
                        b1->setSize(100,200,50,20);
    
                        add(b1);                                               // ПЕРЕДАТЬ ОКНУ
    
                        b1->addActionListener(new adapter_F1(this));
    
                        // Передать объект-обработчик класса adapter_F1, а ему – указатель на окно
    
                        b1=new Button("Стоп");                         // Создать КНОПКУ
    
                        b1->setSize(100,230,50,20);
    
                        add(b1);                                               // ПЕРЕДАТЬ ОКНУ
    
                        b1->addActionListener(this);
    
                        b1->addActionListener(new adapter_F2(this));
    
                        TextField *t1=new TextField(10);            // Создать ТЕКСТ
    
                        t1->setSize(100,250,100,20);
    
                        add(t1);                                                // ПЕРЕДАТЬ ОКНУ
    
                        }
    
            // Вызываются из классов-адаптеров
    
            void F1(ActionEvent ee){
    
                        printf("Обработка в F1 нажатия %s\n",ee.name);
    
            }
    
            void F2(ActionEvent ee){
    
                        printf("Обработка в F2 нажатия %s\n",ee.name);
    
            }

    };

    • TEXT
         в Java специально для компактного описания обработчиков событий введен синтаксис анонимного класса. При создании динамического объекта абстрактного класса (интерфейса) он сразу же доопределяется путем записи недостающих методов  в теле класса в фигурных скобках. При этом он является вложенных и по правилам Java ему доступен по умолчанию контекст объекта внешнего класса-родителя (чего нет в Си++). Таким образом, метод обработки события имеет стандартное имя и каждый раз переписывается в новом экземпляре класса «на лету». Это выглядит примерно так.

    B2.addActionListener(new ActionListener(){ // Тело анонимного класса

    void actionPerformed(ActionEvent M){ pt.setText("Pressed B2"); }

    }

    );