Информатика и технология программирования

         

Система объектов, управляемых сообщениями


Здесь мы рассмотрим очень приближенную модель того, что реально происходит при программировании в объектно-ориентированных библиотеках, основанных на обработке сообщений (или событий) объектами программы. (Например, OWL в Windows или Turbo Vision в DOS) . Система объектов, управляемых сообщениями, должна включать в себя несколько классов, взаимодействие между которыми скрыто от внешнего наблюдателя :



-класс сообщений ;



-базовый класс объектов, управляемых сообщениями ;



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

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


// Класс сообщений -----------------------------------


&#35define ms_NULL 0 // Пустое сообщение


&#35define ms_KEYB 1 // Символ клавиатуры


&#35define ms_TICK 2 // Тик таймера


&#35define ms_MS_MOVE 3 // Перемещение мыши


&#35define ms_MS_CLICK 4 // Кнопка мыши




&#35define ms_ECHO 5 // Ответ объекта с this


&#35define ms_EXIT 6 // Завершение программы


&#35define ms_KILL 7 // Уничтожение объекта


&#35define ms_BORN 8 // Порождение объекта


class MS
{
public:
char code; // Код сообщения


int x,y; // Параметры сообщения


void *p; // Указатель на данные


MS(char,int,int,void *); // Констуктор - создать сообщение


~MS();
void clear()
{ code=ms_NULL; }; // " Очистить" сообщение


};


MS::MS(char c,int xx,int yy,void *q)
{ code = c; x = xx; y = yy; p = q; }


MS::~MS() {}

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

zlist messages; // Сообщения программы

public:
PRG();
~PRG();
void SendEvent(char,int,int,void *);
void run();
};

PRG *OBJ::programm = NULL;





Конструктор связывает объект-программу с объектами класса OBJ путем установки в них статического указателя на самого себя. После этого все объекты могут передавать сообщения методом SendEvent , который просто ретранслируется в аналогичный метод SendEvent в классе PRG. Последний создает объект-сообщение и помещает указатель на него в
конец списка сообщений messages .

// Конструктор: связаться с классом OBJ

PRG::PRG()
{ OBJ::programm = this; }

//---- Прием и запись нового сообщения -------------------

void PRG::SendEvent(char code0,int x0,int y0, void *p0)
{ MS *pm;
pm = new MS(code0,x0,y0,p0);
messages((void *)pm); // Переопределенная операция x(void*) -

} // включить последним

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



// Диспетчер сообщений и объектов -------------------

void PRG::run()
{ MS *pm;
clock_t t;
t = clock();
while(1)
{
for (n=0; (pm = (MS *)messages.Remove(0) !=NULL; n++)
{ // Пока есть сообщения в очереди -

switch (pm-&#62code) // исключить первое

{ // и переключиться по коду

case ms_BORN: // Служебное сообщение от конструктора

objects(pm-&#62p); // объекта - включить в список объектов

break;
case ms_NULL:
break; // Пустое сообщение

case ms_EXIT:
return; // Сообщение о завершении работы

case ms_KILL: // Сообщение об уничтожении процесса -

void *q = pm-&#62p; // Посылается объектом, который хочет

// завершить работу

pp = (OBJ *)objects.Remove(q);
if (pp == NULL) break; // Исключить его из списка




delete pp; // Разрушить динамический объект -

break; // вызвать виртуальный деструктор

default: // Остальные сообщения передаются

int n=objects.size(); // всем объектам в списке

for (m=0; m&#60n; m++)
{ // Извлечь указатель на объект по номеру

OBJ *pp = (OBJ*)objects[m];
// Вызвать виртуальную функцию обработки

// сообщение объектом

pp-&#62HandleEvent(pm);
// Сообщение обработано данным объектом

if (pm-&#62code == ms_NULL) break;
}
}
delete pm; // Уничтожить сообщение

}
}
//----- Проверка источников сообщений ---------------------

if (kbhit()) // Сообщение от клавиатуры

{
pm = new MS(ms_KEYB,(int)getch(),0,NULL);
messages((void *)pm);
}
if ( clock()-t &#62 1) // Сообщение от таймера (часы)

{ t = clock();
pm = new MS(ms_TICK,0,0,NULL);
messages((void *)pm);
}
}}

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

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


Диспетчер исключает динамический объект из списка и уничтожает его (как динамический объект).

Вторая часть диспетчера опрашивает внешние источники сообщений - клавиатуру и часы - и формирует сообщения от них, которые включает в очередь диспетчера.

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

// Деструктор: уничтожение сообщений и объектов ----- --------

PRG::~PRG()
{
while ((MS *p=messages.Remove(0))!=NULL) delete p;
while ((OBJ *q=objects.Remove(0)) !=NULL) delete q;
}

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



// Производный класс прикладной программы --------------------

class myPRG : public PRG
{
public:
myPRG() { MOUSE *pms = new MOUSE; }
};



Основная программа main состоит из двух строчек - создать единственный объект класса
myprog и вызвать для него метод run , поскольку все, что можно было, уже реализовано в предыдущих классах.

void main()
{
myPRG myprog;
myprog.run();
}

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

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


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



// Класс "Видимая точка" ---------------------------

class PT : public OBJ
{ char sym; // Отображаемый символ

int x,y; // Координаты точки

public:
PT(char); // Конструктор

~PT(); // Деструктор

void on(); // Высветить

void off(); // Погасить

void up(); // Перемещения точки

void down();
void left();
void right();
void OnPlace(int,int); // Позиционирование точки

void HandleEvent(MS *); // Обработка сообщений

};
//--------------------------------------------------------------

PT::PT(char c) { sym = c; x = 0; y = 0; }

void PT::off() { gotoxy(x+1,y+1); putch(' '); }

void PT::on() { gotoxy(x+1,y+1); putch(sym); }

void PT::OnPlace(int xx, int yy) {off(); x = xx; y = yy; on(); }

void PT::left() { off(); if (x &#62 0) x--; on(); }

void PT::right() { off(); if (x &#60 79) x++; on(); }

void PT::up() { off(); if (y &#62 0) y--; on(); }

void PT::down() { off(); if (y &#60 24) y++; on(); }

PT::~PT() {off(); }

void PT::HandleEvent(MS *pm) {}



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



// Класс " Курсор - управляемый с клавиатуры"

class MOUSE : public PT
{ public:
void HandleEvent(MS *);
MOUSE();
~MOUSE();
};
//--------------------------------------------------------------

MOUSE::MOUSE() : PT('&#35') // Конструктор - сконструировать

{ OnPlace(40,12); } // точку " &#35" и установить ее на 40,12

void MOUSE::HandleEvent(MS *pm)
{
switch (pm-&#62code)
{ // Реакция курсора на сообщения от клавиатуры

case ms_KEYB:
switch(pm-&#62x)
{
case '8': up(); pm-&#62clear(); break;
case '2': down(); pm-&#62clear(); break;
case '4': left(); pm-&#62clear(); break;
case '6': down(); pm-&#62clear(); break;
case '0': pm-&#62clear(); SendEvent(ms_KILL,0,0,(void *)(OBJ *)this); break;


case '5': pm-&#62clear(); SendEvent(ms_EXIT,0,0,NULL); break;
}
break;
}
}
MOUSE::~MOUSE() {}

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

// Класс "Движущиеся точки" -------------------------

class MOVING : public PT
{ int dx,dy;
public:
void oneStep();
void HandleEvent(MS *);
MOVING(int,int,int,int);
~MOVING();
};

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




Содержание раздела