(原创) 一个通用的C++ 消息总线框架

c++11 boost技术交流群:296561497,欢迎大家来交流技术。

应用开发过程中经常会处理对象间通信的问题,一般都是对象或接口的依赖和引用去实现对象间的通信,这在一般情况下是没问题的,但是如果相互通信的对象很多,可能会造成对象间的引用关系像蜘蛛网一样,这样会导致对象关系很复杂,难以维护的问题,解决这个问题的一个好方法是通过消息总线去解耦对象间大量相互引用的紧耦合的关系。

设计思路:被通信对象向消息总线发布一个主题,这个主题包含消息主题、消息类型和消息处理函数,消息主题标示某个特定的主题,消息类型用来区分标示这个主题会响应某个特定的消息,消息处理函数用来响应该主题的某种消息类型。通信对象向消息总线发送某个特定主和某个特定消息,总线就会根据消息主题和消息类型找到对应的消息处理函数处理该请求。

由于用到了c++11的可变模板参数和lamda表达式,windows上编译需要Compiler Nov 2012 CTP,linux需要GCC4.7以上。

具体代码:

#pragma once
#include <boost/tuple/tuple.hpp>
#include <boost/utility.hpp>
#include <boost/unordered_map.hpp>
#include <boost/any.hpp>

template <typename... Args>
struct Impl;

template <typename First, typename... Args>
struct Impl<First, Args...>
{
    static std::string name()
    {
        return std::string(typeid(First).name()) + " " + Impl<Args...>::name();
    }
};

template <>
struct Impl<>
{
    static std::string name()
    {
        return "";
    }
};

template <typename... Args>
std::string type_name()
{
    return Impl<Args...>::name();
}

class MessageBus : boost::noncopyable
{
public:
    //向某个主题注册主题,需要订阅主题(topic、消息类型)和消息处理函数。    
    template<typename... TArgs, typename TObject, typename TMember>
    void Attach(string strTopic, TObject* Object, TMember Member)
    {         
        std::function<void(TArgs...)> f = std::function<void(TArgs...)>([=](TArgs... arg){(Object->*Member)(arg...);});

        m_map.insert(make_pair(GetKey(strTopic), f));        
    }

    //向某个主题发送消息, 需要主题和消息类型。消息总线收到消息后会找到并通知对应的消息处理函数。
    template<typename... Args>
    void SendReq(string strTopic, Args... args)
    {
        auto range=m_map.equal_range(GetKey(strTopic));
        boost::unordered_multimap<string, boost::any>::iterator it;

        for (it = range.first;  it!= range.second; it++)
        {
            std::function<void(Args...)> f = boost::any_cast<std::function<void(Args...)>>(it->second);
            f(args...);
        }
    }

    //移除某个主题, 需要主题和消息类型
    template<typename... Args>
    void Remove(string strTopic)
    {
        auto it = m_map.find(GetKey(strTopic));
        while(it!=m_map.end())
            m_map.erase(it++);        
    }

private:
    //获得消息键值,通过某个主题和消息类型可以确定观察者
    template<typename... TArgs>
    string GetKey(string& strTopic)
    {        
        return strTopic + type_name<TArgs...>();
    }

private:
    boost::unordered_multimap<string, boost::any> m_map;
};

测试代码:

    MessageBus bus;
    MyStruct st;
    bus.Attach<int,string>("bb", &st, &MyStruct::Test); //注册主题(topic、消息类型、消息处理函数)
    bus.Attach<int,string>("bb", &st, &MyStruct::Test2);
    bus.SendReq<int, string>("bb",0," append"); //发送消息处理请求(主题和消息类型)
    bus.Remove<int, string>("bb"); //移除主题(主题和消息类型)

测试结果:

it is a test: 0 append

it is a test2: 0 append

更新版本,通过万能的函数包装器实现消息总线,使得接口的调用更加通用和一致。

template <typename R=void>
class MessageBus : boost::noncopyable
{
public:
    //注册消息
    template< class... Args, class F, class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
    void Attach(string strKey, F && f)
    {
        std::function<R(Args...)> fn = [&](Args... args){return f(std::forward<Args>(args)...); };
        m_map.insert(std::make_pair(strKey + type_name < Args...>(), std::move(fn)));
    }

    // non-const member function 
    template<class... Args, class C, class... DArgs, class P>
    void Attach(string strKey, R(C::*f)(DArgs...), P && p)
    {
        std::function<R(Args...)> fn = [&, f](Args... args){return (*p.*f)(std::forward<Args>(args)...); };
        m_map.insert(std::make_pair(strKey + type_name < Args...>(), std::move(fn)));
    }
    
    template<class... Args, class C, class... DArgs, class P>
    void Attach(string strKey, R(C::*f)(DArgs...) const, P && p)
    {
        std::function<R(Args...)> fn = [&, f](Args... args){return (*p.*f)(std::forward<Args>(args)...); };
        m_map.insert(std::make_pair(strKey + type_name < Args...>(), std::move(fn)));
    }

    //广播消息,主题和参数可以确定一个消息, 所有的消息接收者都将收到并处理该消息
    template<typename... Args>
    void SendReq(string strTopic, Args... args)
    {
        auto range = m_map.equal_range(strTopic + type_name < Args...>());
        for (auto it = range.first;  it != range.second; it++)
        {
            std::function<R(Args...)> f = boost::any_cast<std::function<R(Args...)>>(it->second);
            f(args...);
        }
    }

    //移除消息
    template<typename... Args>
    void Remove(string strTopic)
    {
        string strMsgType = GetNameofMsgType<Args...>();
        auto range=m_map.equal_range(strTopic+strMsgType);
        m_map.erase(range.first, range.second);    
    }

private:
    std::multimap<string, boost::any> m_map;
};

测试代码:

struct A
{
    void Test(int x){ cout << x << endl; }
    void GTest()
    {
        cout << "it is a test" << endl;
    }
    void HTest(int x) const
    {
        cout << "it is a HTest" << endl;
    }
};

void GG(int x)
{
    cout << "it is a gg" << endl;
}

void GG1()
{
    cout << "it is a GG" << endl;
}

void TestMessageBus()
{
    A a;
    MessageBus<> bus;
    bus.Attach<int>("aa", &A::Test, &a);
    int x = 3;
    bus.SendReq("aa", 3);

    bus.Attach<int>("hh", &A::HTest, &a);
    bus.SendReq("hh", x);
    bus.Attach("bb", &A::GTest, &a);
    bus.SendReq("bb");

    bus.Attach<int>("gg", GG);
    bus.SendReq("gg", 3);

    bus.Attach("gg", GG1);
    bus.SendReq("gg");
}

 

posted on 2013-04-28 11:35  qicosmos(江南)  阅读(9012)  评论(2编辑  收藏  举报

导航