Шаблон проектирования, чтобы избежать понижения при передаче сообщений

Базовый класс MessageHandler имеет производные классы. Они хотели бы передавать сообщения друг другу. Сообщения могут относиться к разным классам, но могут быть созданы для совместного использования базового класса. Как каждый MessageHandler может избежать понижения качества полученного сообщения? Можно ли как-то сделать что-то, что приведет к параметризации шаблона виртуальной функции receiveMessage в MessageHandler?

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

// ...
virtual void MessageHandler::receiveMessage(Message &msg) = 0;
// ...

// to receive a message
void DerivedMessageHandler::receiveMessage(Message& msg)
{
    switch (msg.MsgType()) // enum
    {
        case Message::MessageType::A:
            MessageA& = dynamic_cast<MessageA&>(msg);
            break;

        case Message::MessageType::B:
            MessageB& = dynamic_cast<MessageB&>(msg);
            break;
        default:
            // don't process unknown messages
            break;
    }
}

// to send a message
list<MessageHandler> mhList;
// populate list
for (MessageHandler& mh : mhList)
{
    mh.receiveMessage(msg);
}

Я знаю, что не могу этого сделать, но что-то вроде

template <typename M>
void MessageHandler::receiveMessage(M& msg) {}

И сделать так, чтобы каждый DerivedMessageHandler специализировался на M? Каким может быть шаблон проектирования, который позволяет каждому обработчику работать с поддерживаемыми им объектами сообщений?


person Oliver Zheng    schedule 17.02.2012    source источник
comment
Сообщение на самом деле представляет собой просто структуру POD, содержащую различные элементы данных, описывающие их.   -  person Oliver Zheng    schedule 17.02.2012


Ответы (3)


Это довольно легко сделать. Обычно есть две альтернативы:

Boost.Вариант

Вместо того, чтобы передавать производный класс, просто перечислите возможные типы, которыми может быть сообщение. Эти типы не обязательно должны быть производными друг от друга. Оберните эти типы в boost::variant:

typedef boost::variant<MessageData1, MessageData2, MessageData3, ...> MessageData;

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

Boost.Любой

Просто передайте что-нибудь с boost::any:

void MessageHandler::receiveMessage(const boost::any &msg)
{
  const MessageType1 *pMsg = boost::any_cast<MessageType1>(&msg);
  if(!pMsg)
    //Cannot process
    return;

  //Process message.
}

boost::any похож на типобезопасный void*. Он запоминает точный тип, который был в него помещен, и любая попытка привести его к чему-то отличному от того, что в нем хранится, потерпит неудачу. boost::any может хранить что угодно, отсюда и название.

Он также имеет семантику значений, поэтому его можно копировать, как и его содержимое.

person Nicol Bolas    schedule 17.02.2012
comment
Вариант именно то, что мне нужно. Благодарю вас! Однако мне интересно, какая магия шаблонов здесь, чтобы это произошло. - person Oliver Zheng; 17.02.2012

Если я правильно понимаю ваш вопрос, вам просто нужно прямое наследование с виртуальной функцией. Что-то вроде:

class BaseMessage 
{
    public:
    virtual ~BaseMessage() {}

    virtual void processMsg() = 0;
};

class MessageA : public BaseMessage
{
    public:
    MessageA() {}
    virtual ~MessageA() {}    
    virtual void processMsg()
    {
        // ... do something for MessageA ...
    }
};

class MessageB : public BaseMessage
{
    public:
    MessageB() {}
    virtual ~MessageB() {}    
    virtual void processMsg()
    {
        // ... do something for MessageB ...
    }
};

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

std::auto_ptr<BaseMessage> m(mailbox.getMessage()); // Returns whatever msg is sent to your handler
m->processMsg();
person RC.    schedule 17.02.2012
comment
Сообщение просто содержит параметры для различных обработчиков сообщений. Что-то делается внутри обработчиков, а не сообщений. - person Oliver Zheng; 17.02.2012
comment
@OliverZheng, это лучший подход, поскольку в противном случае обработчики должны знать о каждом типе сообщений, представьте себе масштабирование его в системе с сотнями сообщений. - person Vicente Bolea; 12.01.2017
comment
Проблема этого решения в том, что оно привязывает поведение к объекту данных. Представьте, что вам нужны сотни операций над объектом данных, вы не добавите их все в интерфейс событий, не так ли? Вы можете поддерживать бесконечное количество сервисов с понижающим приведением, не касаясь подписи объекта данных (события). Я ищу ответ на аналогичную проблему: stackoverflow.com/questions/55695922/ - person gavenkoa; 17.04.2019

Вы можете использовать шаблон посетителя.

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

class Visitor;
class BaseMsg {
//..
public:
virtual void acceptVisitor(Visitor * v) = 0;
};

class Msg1;
class Msg2;
class Visitor {     
// You can put here pure virtuals for sure every visitor will implement them
public:
virtual void action (Msg1 * msg) = 0;
virtual void action (Msg2 * msg) = 0;
};

class Msg1: public BaseMsg {
//..
public:
void acceptVisitor(Visitor * v){v->action(this);}
};

class Msg2: public BaseMsg  {
//..
public:
void acceptVisitor(Visitor * v){v->action(this);}
};



class Visitor1 : public Visitor {
// ...
public:
void action (Msg1 * msg) {/*...*/ cout << "I like the message!\n";}
void action (Msg2 * msg) {/*...*/ cout << "I hate the message!\n";}
// more messages and actions for them
};

class Visitor2 : public Visitor{
// ...
public:
void action (Msg1 * msg) {/*...*/ cout << "Just fine\n";}
void action (Msg2 * msg) {/*...*/ cout << "Sorry, I'm busy\n";}
// more messages and actions for them
};

int main() {

BaseMsg * a = new Msg1;
BaseMsg * b = new Msg2;

Visitor * act = new Visitor1;
Visitor * lazy = new Visitor2;
// ............
// somewhere in a deep deep forest of your code

a->acceptVisitor(act);
b->acceptVisitor(act);

// somewhere else

a->acceptVisitor(lazy);
b->acceptVisitor(lazy);

delete act;
delete lazy;
delete a;
delete b;
return 0;
}  

Выход:

  • Мне нравится сообщение!
  • Я ненавижу сообщение!
  • Просто хорошо
  • Прости я занят
person Tim Kachko    schedule 17.02.2012
comment
Это решение плохо масштабируется при наличии большого количества различных сообщений. Так как каждый посетитель должен знать о каждом сообщении - person Vicente Bolea; 12.01.2017