информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
Spanning Tree Protocol: недокументированное применениеВсе любят медАтака на Internet
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Бэкдор в xz/liblzma, предназначенный... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
все доски
FAQ
IRC
новые сообщения
site updates
guestbook
beginners
sysadmin
programming
operating systems
theory
web building
software
hardware
networking
law
hacking
gadgets
job
dnet
humor
miscellaneous
scrap
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
[C++] ШАГ 5. Сообщения пользователя. Слон родил мыша 27.07.03 06:23  Число просмотров: 2363
Автор: void <Grebnev Valery> Статус: Elderman
Отредактировано 27.07.03 06:26  Количество правок: 1
<"чистая" ссылка>
Простите, убогого, но немогу кончить. Уже семь дней...

*** ШАГ 5. Получился Медиатор *

На шаге 4 мы придумали не такой уж плохой механизм рассылки и обработки широковещательных сообщений. Я говорю мы, поскольку, на форумах кивали в сторону возможности использования SendMessage для обработки сообщений. При этом вызов методов CMessanger, например, CMessanger::EnableControls, CMessanger::ClearControls удобнее, чем рассылка соответствующих сообщений при помощи «универсального» метода CMessanger::SendMessage. В последнем случае необходимо по условному переходу ещё передать управление конкретному обработчику в зависимости WPARAM wParam, LPARAM lParam.
Однако здесь привлекательна, лишь форма (использование theMsg.EnableControls вместо theMsg.SendMessage(UM_NOTIFY, IDW_ALL, UM_ERASE, 0)). По существу CMessanger::EnableControls всё равно используется метод CMessanger::SendMessage, а тот в свою очередь использует метод рассылки сообщений MFC CWnd-объектам или API, см. врезку кода выше. Да, погнавшись за внешней стороной дела – замоделировались. Как справедливо, заметил один из участников форума, так мы «едем из Москвы в Подмосковье через Колыму». Надо сказать, что это участник форума весьма вежливый человек, и выразился мягко, поскольку едем не только через Колыму, но … как мудрёно! А сколько «технического» кода придётся написать? То, что надо написать строчки кода для каждого из методов ИНТЕРФЕЙСА наших команд в CMessanger (EnableControls, ClearControls и т.д) – это понятно, как бы мы не делали. Неизбежные накладные расходы, если необходим броадкастинг. Но то, что для каждого CWnd-класса придётся ещё и дополнительные макросы писать – последняя капля. Макросы месадж-мапа простые, но их придётся писать для каждого класса CWnd-объектов, которые участвуют в широковещательной рассылке. Ну и сами методы CWnd-объектов. Где уж там до useful programming style... Всё в топку!

Для того, чтобы отказаться от MS-SendMessage, надо сделать так, чтобы объекты, участвующие в оповещениях о событиях приложения, имели соответствующие методы, которые они наследовали бы от некого объекта CMsgItem, уже имеющего соответствующий командный «интерфейс». Для вызова виртуальных методов этого «интерфейса» CMessаnger должен знать адрес vtb CMsgItem, а не указатель на сам CWnd-объект. Расширим определение CWndInfo, добавив CMsgItem* m_pMsgItem, и немного упростим мембер-функции CMessаnger:


///////////////////////////////////////////////////
//
// file “utilwnd.h”
//
///////////////////////////////////////////////////

#include <afxtempl.h>

#define IDW_ALL 0 // send message to all CWnd objects

class CMsgItem; // forward declaration

////////////////////////////////////////////////////
// CMessanger class

class CMessanger {
public:
class CWndInfo { // object info class
public:
void* m_pWnd;// pointer to the object registered
UINT ID; // user symb.const. for the object registered
CMsgItem* m_pMsgItem;
public:
CWndInfo() : m_pWnd(NULL), m_pMsgItem(NULL), ID(0) {};
CWndInfo(void* pWnd, CMsgItem* pMsgItem = NULL, UINT ID_WND = 0)
{
m_pWnd = pWnd; m_pMsgItem = pMsgItem; ID = ID_WND;
}
~CWndInfo() {};
};
public:
CMessanger() : status(0) {};

// register object in CWndInfo array. Save pointer to the object,
// command inteface and user defined symbolic constant
// ID_WND for this object
void Register(void* pWnd, CMsgItem* pMsgItem, UINT ID_WND)
{
m_aWnd.Add(new CWndInfo(pWnd, pMsgItem, ID_WND));
}

// get CWnd* pointer from CWndInfo array by user defined constant ID_WND
CWnd* GetWnd(UINT ID_WND)
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == m_aWnd.GetAt(i)->ID)
return (CWnd*) (m_aWnd.GetAt(i)->m_pWnd);
return NULL;
}
// complete status of message
UINT GetStatus(void) { return status; }
void SetStatus(UINT _status) { status = _status; }

//**** main message handling****
virtual bool FillControls (UINT message, UINT ID_WND = IDW_ALL) {return true;};
virtual bool DisplayControls(UINT message,UINT ID_WND = IDW_ALL) {return true;};

//**** additional message handling****
// broadcast (ID_WND == IDW_ALL) or direct message handling
void SendMessage(UINT message, UINT ID_WND = IDW_ALL,
WPARAM wParam = 0,LPARAM lParam = 0);

// special message handling
void EnableControls(bool Еnable, UINT ID_WND = IDW_ALL);
void ClearControls(UINT ID_WND = IDW_ALL);

private:
CArray <CWndInfo*, CWndInfo*> m_aWnd;
UINT status;
};

extern CMessanger theMsg;


////////////////////////////////////////////////////
// CMsgItem class

class CMsgItem {
public:
void Register(CMessanger* pMsg, void* pWnd, UINT ID_WND)
{
pMsg->Register( pWnd , this, ID_WND);
};
//**** additional message handling****
virtual void SendMessage(UINT message, WPARAM wParamMsg=0, LPARAM lParam=0){}

// special message handling
virtual void EnableControls(bool Enable){}
virtual void ClearControls (void) {}
};
//-----------------------------------------------------



///////////////////////////////////////////////////
//
// file “utilwnd.cpp”
//
///////////////////////////////////////////////////
#include "StdAfx.h"
#include "utilwnd.h"

CMessanger theMsg;

// general broadcast (ID_WND == IDW_ALL) or direct message handling
void CMessanger::SendMessage(UINT message , UINT ID_WND, WPARAM wParam, LPARAM lParam)
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == IDW_ALL|ID_WND == m_aWnd.GetAt(i)->ID )
m_aWnd.GetAt(i)->m_pMsgItem->SendMessage(message, wParam, lParam);
}
//-------------------------------------------------------------
// special message handling
void CMessanger::EnableControls(bool Enable, UINT ID_WND)
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == IDW_ALL|ID_WND == m_aWnd.GetAt(i)->ID )
m_aWnd.GetAt(i)->m_pMsgItem->EnableControls( Enable );
}
//-------------------------------------------------------------
void CMessanger::ClearControls( UINT ID_WND )
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == IDW_ALL|ID_WND == m_aWnd.GetAt(i)->ID )
m_aWnd.GetAt(i)->m_pMsgItem->ClearControls();
}
//-------------------------------------------------------------


Как видно, класс CMsgItem представляет некий почти «абстрактный» командный интерфейс, и состоит только из виртуальных методов и дополнительной функции Register (одноимённая функция есть у CMessanger), которая введена косметически, для удобства регистрации. Здесь перечисляются все команды, которые необходимо выполнять согласно введённому раннее enum app_state. Выше для примера приведены только два метода. Дополнительно оставлен метод SendMessage, который теперь не имеет ничего общего с MFC или API(одноименную функцию обрабатывает CMessanger исключительно сам). Это сделано на «всякий пожарный», если во всех классах CWnd-объектов, наследующих, от CMsgItem, приемлемо ограничиться обработкой сообщений-команд только в SendMessage, например, в свиче, вместо переопределения большого числа мембер-функции EnableControls, ReadControls, и т.д в классе, derived от CMsgItem . Если кому-то нравится это, то последние можно удалить из определения CMsgItem и CMessanger. Кстати, может стоит это сделать? Последнее, что занудно уточню, и что и так всем понятно - методы CMsgItem никогда не вызываются напрямую при передаче сообщений. Это делает CMessanger (выступая от имени того, кто вызвал его соответствующий метод), вызывая переопределённый метод derived CWnd-класса(кого здесь стошнило – извините).

В классе CMessanger осталось всё без изменений, за исключение тех, что связаны с появлением на свет CMsgItem, и GetStatus(void), SetStatus(UINT _status). Об этих двух методах, как и о набивших всем оскомину функциях FillControls/DisplayControls – чуть ниже в резюме. Кроме того, в CWndInfo указатель на оконный объект, который пока И НЕ НУЖЕН, переделан на void*. Если потребуется этот CWnd-объект (а так, в принципе любого класса), то для использования его методов в теле FillControls/DisplayControls можно вызвать немного переделанную функцию GetWnd.

Как этим всем пользоваться? Например, если диалог CMainDialog будем наследовать и от CMsgItem, то так:

///////////////////////////////////////////////////////////////////
//
// CMainDialog dialog declaration
//
///////////////////////////////////////////////////////////////////

class CMainDialog : public CDialog, CMsgItem
{
// CDialog, CMainDialog declarations


public:

//////////////////////////////////////////////////
// CMsgItem interface general method
void SendMessage(UINT message, WPARAM wParamMsg, LPARAM lParam)
{
switch (message)
{
case UM_READ:
// Do something
break;
case UM_ERASE:
// Do something
break;
};
}

// CMsgItem interface special methods
void EnableControls(bool Enable)
{
// Do something
}
void ClearControls (void)
{
// Do something
}

};


///////////////////////////////////////////////////////////////////////
//
// CMainDialog dialog implementation
//
///////////////////////////////////////////////////////////////////////

//--------------------------------------------------------------------
BOOL CMainDialog::OnInitDialog()
{
CDialog::OnInitDialog();

// Inherited CMsgItem metod
Register(&theMsg, this, IDW_MAIN_DIALOG);

return TRUE;
}
//--------------------------------------------------------------------
void CMainDialog::OnBnClickedEditButton()
{
// «одним махом», широковещательной рассылкой очищаем все контролы
// тех объектов, о которых известно theMsg
theMsg.ClearControls();
}
//--------------------------------------------------------------------

В приведённом выше коде не хватает некого метода CMessanger – UnRegister. Не то, что бы я его не показал на форуме. Я просто про него забыл!!! из-за своего балбесянства. Последствия, которые могут быть, ясны – крах. Про него мне подсказал пост одного из участников форумов. Ниже, без персоналий привожу ЕГО фрагмент кода, где такой метод void CMediator::UnregisterRecipient(CCommandInterface *Rec) присутствует. Для тех, кто заинтересовался, и кто ещё не смотрел подобную проблематику, ооооочень рекомендую посмотреть код ЭТОГО человека. Прошу модераторов простить за перепост. Там же комментарии. Код чище, стильнее, чем у меня. Так, что некоторые вещи своего «рабочего» класса CMessanger я наглым образом перепишу, как У НЕГО:

//--------------------------------------------------------------------
//H-файл

class CCommandInterface // у меня это CMsgItem
{
public:
virtual void Execute(CCommand *cmd) = 0;
};

class CMediator // у меня это CMessanger
{
public:
static CMediator* Instance();

void RegisterRecipient(CCommandInterface *newRec);
void UnregisterRecipient(CCommandInterface *Rec);
void SendMessage(CCommand *cmd);
void SendMessage(CCommandInterface *whom, CCommand *cmd);

protected:
CMediator();
private:
static CMediator* m_instance;
CArray<CCommandInterface*, CCommandInterface*> m_recipients;
};

//--------------------------------------------------------------------
//CPP-файл

CMediator* CMediator::m_instance = NULL;

CMediator::CMediator()
{
}

CMediator* CMediator::Instance()
{
if (m_instance == NULL)
{
m_instance = new CMediator;
ASSERT(m_instance);
}
return m_instance;
}

void CMediator::RegisterRecipient(CCommandInterface *newRec)
{
for (int i = 0; i < m_recipients.GetSize(); i++)
{
if (newRec == m_recipients[i])
{
return;
}
}
m_recipients.Add(newRec);
}

void CMediator::UnregisterRecipient(CCommandInterface *Rec)
{
for (int i = 0; i < m_recipients.GetSize(); i++)
{
if (Rec == m_recipients[i])
{
m_recipients.RemoveAt(i);
return;
}
}
}

void CMediator::SendMessage(CCommand *cmd)
{
for (int i = 0; i < m_recipients.GetSize(); i++)
{
m_recipients[i]->Execute(cmd);
}
}

void CMediator::SendMessage(CCommandInterface *whom, CCommand *cmd)
{
for (int i = 0; i < m_recipients.GetSize(); i++)
{
if (whom == m_recipients[i])
{
m_recipients[i]->Execute(cmd);
}
}
}

//--------------------------------------------------------------------
//CPP- где что-то делаем

любое заинтересованное окно получает указатель на медиатор таким и только таким образом (конструктор медиатора защищенный):
CMediator *mediator = CMediator::Instance();

и регистрирует себя, скажем в своем конструкторе
mediator->RegisterRecipient((CCommandInterface*)this);

В деструкторе, соответственно разрегистрирует
CMediator *mediator = CMediator::Instance();
mediator->UnregisterRecipient((CCommandInterface*)this);

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

Человек, который написал этот код, не участвует в данном форуме. Поэтому любая критика в его адрес была бы неуместной. Это моя просьба НЕ к тем, кто уже участвует в топике данного форума, т.к. это люди воспитанные и интеллигентные. В части построения GUI - критика тоже была бы не уместна, поскольку человек решает несколько иные задачи. Насколько я понял, это те задачи, для которых собственно и были придуманы паттерны mediator и observer. Так, что этот человек НЕ ЯВЛЯЕТСЯ ДАЖЕ МОИМ СТОРОННИКОМ. Он как бы сказал: «Посмотри. Может сгодится…». Мне сгодилось.


*РЕЗЮМЕ И ДОПОЛНЕНИЯ К ШАГ 5**

1) Можно посылать широковещательные сообщения всем объектам сразу, или в отдельности при помощи медиатора CMessanger, выполняя рассылку без использования SendMessage MFC или API.

2) То, что нет SendMessage MFC или API – это хорошо. Не сложно увидеть, что наряду с CWnd–объектами (derived видов и диалогов и т.д.), для которых выше я и «шагал», CMessanger умеет рассылать команды о состояниях enum app_states ЛЮБЫМ объектам. Создайте не CWnd-класс, например, некий CDatabaseOperations, который будет наследовать от CMgItem, и он тоже может быть включён в общую цепочку обработки сообщений.

3) В CMessanger оставлены виртуальные методы FillControls/DisplayControls. Простая широковещательная рассылка в ряде случаев может привести к «нежелательным эффектам», т.к. иногда необходимо контролировать ПОСЛЕДОВАТЕЛЬНОСТЬ, в которой объекты-подписчики будут выполнять команды CMessanger и РЕЗУЛЬТАТЫ этой обработки. Посредник CMessanger - совсем простой и нечего такого не умеет делать. Приведу цитату общего характера относительно паттерна Observer, где тоже присутствует броадкаст:

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

Бороться с негативом ясно, как. Есть следующие возможности:
А) для бродкаст-манагера «моделировать» асинхронную обработку подписчиками. Сложность решения возрастает неимоверно.

Б) делать умных подписчисиков, запрашивающих стэйт и другое, пока даже не знаю, что…
В) дробить сообщения на более мелкие, например, UM_READ - на UM_READ и UM_XXX_READ, что также не универсально и может перегружать логику приложения событиями
Г) делать что-нибудь нууууууууууу, совсем уж простое. Это я и делаю, сохраняя FillControls и DisplayControls в качестве виртуальных методов «медиатора». Как можно использовать FillControls и DisplayControls для группы подписчиков??? тоже понятно – если можно, то обработать мессагу бродкастом. Если необходим анализ очерёдности, или ещё чего (о чём можно знать только реализуя это в дочке CMessanger конкретного проекта) – то сделаем это. Короче говоря, FillControls и DisplayControls – это дополнительные посредники, вернее дополнительное средство анализа в посреднике (медиаторе). Если так не делать, то прямая дорога к п. А)Б)В), имхо. Например, можно было бы делать такой вызов theMsg.FillControls(UM_READ_DB):

//---------------------------------------------
// h-file
class CMyMsg: public CMessanger
{
public:
bool FillControls (UINT message);
};
extern CMyMsg theMsg;


//----------------------------------------------------
// cpp-file
bool CMyMsg::FillControls(UINT message)
{
switch (message)
{
case UM_ERASE:
ClearControls();


break;
case UM_READ_DB:
FillControls(UM_ERASE);
if( GetStatus() == ST_XXXX )
((Cxxx*) GetWnd(IDW_XXXX))->XXXX();


break;
}
return true;
}
//----------------------------------------------------

PS.
Ещё раз благодарю всех тех, кто откликнулся. Уже не обещаю, что дальше не буду писать столь пространно и столь длинные посты, т.к., думаю сделать завтра последний пост типа «ИТОГИ. КАК БЫТЬ?».

To amirul
amirul, там я хотел бы сделать перепост одного хорошего фрагмента твоего ответа мне. Этика, понимаш… Буду писать только хорошее. Если не возражаешь, то я сделаю это, поскольку «колеблющиеся в ООП» могли пропустить ЭТО между прочим.
<programming> Поиск 






Rambler's Top100
Рейтинг@Mail.ru


  Copyright © 2001-2024 Dmitry Leonov   Page build time: 1 s   Design: Vadim Derkach