Qt 之 开源事件总线模块
libgitlevtbus
用到了libgitlevtbus
(libgitlevtbus)[https://github.com/lheric/libgitlevtbus]
- 介绍
libgitlevtbus 是一个基于Qt的开源的事件总线(消息总线 ) , BSD lisence. - 特征
1. Easy to use (c++11 feature supported: lambda expression, member function callback, ...) //C++11新特性,lambda表达式, 成员函数回调
2. Custom event support (carry custom parameters) //用户事件支持(可携带用户自定义的参数)
3. Events can be deliverd across threads //事件可以跨线程
- Demo
#include "gitlmodule.h"
#include <QDebug>
int main(int argc, char *argv[])
{
GitlModule cModule;
/// subscribe to an event
cModule.subscribeToEvtByName("I am a test event",
[](GitlEvent& rcEvt)->bool
{
qDebug() << "Hello GitlEvtBus!";
return true;
}
);
GitlEvent cEvent("I am a test event"); ///< create an event
cEvent.dispatch(); ///< dispatch
/// output: "Hello GitlEvtBus!"*/
return 0;
}
模型
模块
该事件总线模型主要有以下几个类构成:
- Event
- EventBus
- Module
- ModuleDelegate
Event
该类是事件类,主要内容有:
- 事件名称
- 事件是否带有参数
- 事件参数的设置和获取
- 事件的发布(dispatch):调用Bus的Post接口,将事件派发到总线上,所有订阅该事件的module都会收到消息
- gitlevent.h
#ifndef GITLEVENT_H
#define GITLEVENT_H
#include <QString>
#include <QMap>
#include <QVariant>
#include "gitldef.h"
#include "gitleventparam.h"
class GitlModule;
class GitlEventBus;
/*!
* \brief The GitlEvent class represents an event.
* If you want to create an custom event by inherit GitlEvent, you ***MUST***
* reimplement the 'clone' method in this class. This can be done by adding
* VIRTUAL_COPY_PATTERN(subclassname) in the subclass. Otherwise the application
* may crash
*/
class GitlEvent
{
/// virtual copy pattern, please add this macro to all the subclass
CLONABLE(GitlEvent)
public:
GitlEvent( const QString& strEvtName );
GitlEvent();
virtual ~GitlEvent() {}
/*!
* \brief hasParameter if this event carries a specific parameter
* \param strParam parameter name
* \return
*/
bool hasParameter(QString strParam) const;
/*!
* \brief getParameter get the value of a specific parameter
* \param strParam parameter name
* \return parameter value, if it does not exist, return a default-constructed QVariant
*/
QVariant getParameter(const QString& strParam ) const;
/*!
* \brief setParameter set the value of a specific parameter
* \param strParam parameter name
* \param rvValue parameter value
* \return
*/
bool setParameter(const QString& strParam, const QVariant& rvValue);
/*!
* \brief dispatch dispatch this event to event bus, all module subscribed to this event name will be notified.
* \param pcEventBus If pcEventBus is NULL, it will find a global (default) event bus and post the event onto the bus.
* Or you can specify another event bus.
*/
void dispatch(GitlEventBus *pcEventBus = NULL) const;
protected:
ADD_CLASS_FIELD(QString, strEvtName, getEvtName, setEvtName) ///< event name
ADD_CLASS_FIELD_NOSETTER(GitlEventParam, cParameters, getParameters) ///< event parameters-value pair
};
- gitlevent.cpp
#include "gitlevent.h"
#include "gitlmodule.h"
#include <QDebug>
#include "gitleventbus.h"
#include <QSharedPointer>
GitlEvent::GitlEvent( const QString& strEvtName )
{
this->m_strEvtName = strEvtName;
}
GitlEvent::GitlEvent()
{
this->m_strEvtName = "UNKNOWN";
}
bool GitlEvent::hasParameter(QString strParam) const
{
return m_cParameters.hasParameter(strParam);
}
QVariant GitlEvent::getParameter(const QString& strParam ) const
{
return m_cParameters.getParameter(strParam);
}
bool GitlEvent::setParameter(const QString& strParam, const QVariant& rvValue)
{
m_cParameters.setParameter(strParam, rvValue);
return true;
}
void GitlEvent::dispatch(GitlEventBus* pcEventBus) const
{
if(pcEventBus == NULL)
GitlEventBus::getInstance()->post(*this);
else
pcEventBus->post(*this);
}
EventBus
事件总线,在实际应用中,可以有多条事件总线,每条总线挂接不同的Module.
- 单例模式,在该类中,存在该类型的单例模式,当module未指定某个Bus时, 默认情况都使用该单例
- register: 将ModuleDelegate 的denotate关联至该总线的post接口发出的eventTriggered信号
- post: 将Event传递至总线。上述的Event中的dispatch接口,最终调用的某个Bus对象的post接口。
EventBus的核心: 注册ModuleDelegate, 发布Event 消息
- gitleventbus.h
#ifndef GITLEVENTBUS_H
#define GITLEVENTBUS_H
#include <QList>
#include <QObject>
#include <QMutex>
#include <QMutexLocker>
#include <QSharedPointer>
#include "gitldef.h"
#include "gitlevent.h"
#include "gitlmodule.h"
class GitlModuleDelegate;
/*!
* \brief The GitlEventBus class represents the event bus
*/
class GitlEventBus : public QObject
{
Q_OBJECT
private:
GitlEventBus();
public:
/*!
* \brief create The safe way to explictly create a new event bus
* \return
*/
static GitlEventBus *create();
/*!
* \brief registerModule connect a module to the event bus
* \param pcModule
* \return
*/
bool registerModule(GitlModuleDelegate *pcModule);
/*!
* \brief unregisterModule disconncet a module from the event bus
* \param pcModule
* \return
*/
bool unregisterModule(GitlModuleDelegate *pcModule);
public slots:
/*! send event to event bus
*/
void post(const GitlEvent &rcEvt) const;
signals:
/*! message to send
*/
void eventTriggered( QSharedPointer<GitlEvent> pcEvt ) const;
///SINGLETON design pattern
SINGLETON_PATTERN_DECLARE(GitlEventBus)
};
#endif // GITLEVTBUS_H
- gitleventbus.cpp
#include "gitleventbus.h"
#include <QDebug>
SINGLETON_PATTERN_IMPLIMENT(GitlEventBus)
GitlEventBus::GitlEventBus()
{
}
GitlEventBus *GitlEventBus::create()
{
return new GitlEventBus();
}
/*! connect a module to the event bus
*/
Q_DECLARE_METATYPE( QSharedPointer<GitlEvent> )
bool GitlEventBus::registerModule(GitlModuleDelegate* pcModule)
{
qRegisterMetaType< QSharedPointer<GitlEvent> >("QSharedPointer<GitlEvent>");
connect(this, SIGNAL(eventTriggered(QSharedPointer<GitlEvent>) ),
pcModule, SLOT (detonate (QSharedPointer<GitlEvent>) ),
Qt::AutoConnection );
return true;
}
bool GitlEventBus::unregisterModule(GitlModuleDelegate *pcModule)
{
return disconnect(this, NULL, pcModule, NULL);
}
/*! send event to event bus
*/
void GitlEventBus::post(const GitlEvent& rcEvt) const
{
QSharedPointer<GitlEvent> pcEvtCopy( rcEvt.clone() );
/// notify modules
emit eventTriggered(pcEvtCopy);
}
Module
Module 和 ModuleDelegate 使用了委托模式, 将实际的工作都交给了ModuleDelegate处理了。
委托模式:
一个对象接收到了请求,但是自己不处理,交给另外的对象处理,就是委托模式,例如 老板接到了活,
然后把活转手给了工人去做。
这里的Module有一个私有成员: ModuleDelegate, Event其实并不知道这一层关系的存在,在它眼里,只有BusEvent。
Module的行为(动作), 最后都转化成ModuleDelegate去执行了, 仿佛只是套了个壳。
- gitlmodule.h
#include <QSharedPointer>
#include "gitldef.h"
#include "gitlevent.h"
#include "gitlmoduledelegate.h"
class GitlEventBus;
/*!
* \brief The GitlModule class represents a module
*/
class GitlModule
{
public:
/**
* @brief GitlModule Represents a module in the event bus. It will keep listening to events
* in the event bus and catch those it is interested in.
* @param pcEventBus If pcEventBus it will find a gloabl event bus using singleton pattern.
* Or you can specify an exsiting event bus.
*/
GitlModule(GitlEventBus* pcEventBus = NULL);
/*!
* \brief subscribeToEvtByName Subscribe to an event
* \param strEvtName event name
* \param pfListener listener callback function
*/
void subscribeToEvtByName(const QString& strEvtName,
const GitlCallBack& pfListener );
/*!
* \brief unsubscribeToEvtByName Unsubscribe to an event
* \param strEvtName event name
*/
void unsubscribeToEvtByName( const QString& strEvtName );
/*!
* \brief dispatchEvt Dispatch an event
* \param rcEvt event
*/
void dispatchEvt(GitlEvent &rcEvt );
/*!
* \brief setModuleName Set the name of this module. That's ok if you do not
* give a name to this module. But for better debugging, we recommend you name it.
* \param strModuleName name for this module
*/
void setModuleName(QString strModuleName );
/**
* @brief getEventBus Get the event bus that this module is attached to
* @return
*/
GitlEventBus* getEventBus();
/*!
* \brief detach Detach the module
*/
void detach();
/*!
* \brief attach Attach the module to a new event bus
* \param pcEventBus
*/
void attach(GitlEventBus *pcEventBus);
/// Delegate pattern
/// Avoiding this class becoming a subclass of QObject
/// (GUI class is based on QOBject, but QObject doesn't support virtual inheritance).
ADD_CLASS_FIELD_PRIVATE( GitlModuleDelegate, cDelegate )
};
#endif // GITLMODULE_H
- gitlmodule.cpp
#include "gitlmodule.h"
#include "gitleventbus.h"
#include <QDebug>
GitlModule::GitlModule(GitlEventBus *pcEventBus) :
m_cDelegate(this, pcEventBus)
{
}
void GitlModule::subscribeToEvtByName( const QString& strEvtName,
const GitlCallBack& pfListener )
{
return m_cDelegate.subscribeToEvtByName(strEvtName, pfListener);
}
void GitlModule::unsubscribeToEvtByName( const QString& strEvtName )
{
return m_cDelegate.unsubscribeToEvtByName(strEvtName);
}
void GitlModule::dispatchEvt( GitlEvent& rcEvt )
{
m_cDelegate.dispatchEvt(rcEvt);
}
void GitlModule::setModuleName( QString strModuleName )
{
m_cDelegate.setModuleName(strModuleName);
}
GitlEventBus *GitlModule::getEventBus()
{
return m_cDelegate.getGitlEvtBus();
}
void GitlModule::detach()
{
m_cDelegate.detach();
}
void GitlModule::attach(GitlEventBus *pcEventBus)
{
m_cDelegate.attach(pcEventBus);
}
ModuleDelegate
由于真正干活的是这位老兄,所以,它除了和Module具有类似的接口,还需要额外维护一些私有成员,比如Module的名字,Event事件和回调接口的QMap关系表。
- gitlmoduledelegate.h
#ifndef GITLMODULEDELEGATE_H
#define GITLMODULEDELEGATE_H
#include <QObject>
#include <QMap>
#include <QMutex>
#include <QMutexLocker>
#include <QSharedPointer>
#include <functional>
#include "gitldef.h"
#include "gitlevent.h"
class GitlModule;
class GitlEventBus;
///
/// \brief GitlCallBack gitl event callback function
///
typedef std::function<bool (GitlEvent&)> GitlCallBack;
class GitlModuleDelegate : public QObject
{
Q_OBJECT
friend class GitlModule; //can access the GitlModule
private:
explicit GitlModuleDelegate(GitlModule *pcDelegator, GitlEventBus *pcEventBus = NULL);
public:
/*!
* \brief subscribeToEvtByName listening to an event by name
* \param strEvtName event name
*/
void subscribeToEvtByName( const QString& strEvtName,
GitlCallBack pfListener );
/*!
* \brief subscribeToEvtByName not listening to an event by name
* \param strEvtName event name
*/
void unsubscribeToEvtByName( const QString& strEvtName );
/*!
* \brief dispatchEvt dispatch an event to subscribers
* \param pcEvt event
*/
void dispatchEvt(const GitlEvent &rcEvt ) const;
/*!
* \brief detach Detach the module
*/
void detach();
/*!
* \brief attach Attach the module to a new event bus
* \param pcEventBus
*/
void attach(GitlEventBus *pcEventBus);
public slots:
/*!
* \brief detonate notifyed by event bus
* \param cEvt
* \return
*/
bool detonate( QSharedPointer<GitlEvent> pcEvt );
protected:
bool xIsListenToEvt(const QString& strEvtName);
ADD_CLASS_FIELD( QString, strModuleName, getModuleName, setModuleName )
ADD_CLASS_FIELD_PRIVATE( CONCATE(QMap<QString, GitlCallBack>), cListeningEvts )
ADD_CLASS_FIELD_NOSETTER( GitlEventBus*, pcGitlEvtBus, getGitlEvtBus )
ADD_CLASS_FIELD_PRIVATE(GitlModule*, pcDelegator)
};
#endif // GITLMODULEDELEGATE_H
- gitlmoduledelegate.cpp
#include "gitlmoduledelegate.h"
#include "gitleventbus.h"
#include <QDebug>
#include <iostream>
using namespace std;
GitlModuleDelegate::GitlModuleDelegate(GitlModule *pcDelegator, GitlEventBus* pcEventBus)
{
m_pcDelegator = pcDelegator;
if(pcEventBus == NULL)
m_pcGitlEvtBus = GitlEventBus::getInstance();
else
m_pcGitlEvtBus = pcEventBus;
m_pcGitlEvtBus->registerModule(this); //调用eventBus注册moduleDelegate
m_strModuleName = "undefined_module_name";
}
void GitlModuleDelegate::subscribeToEvtByName(const QString& strEvtName, GitlCallBack pfListener )
{
m_cListeningEvts.insert(strEvtName, pfListener);
return;
}
void GitlModuleDelegate::unsubscribeToEvtByName( const QString& strEvtName )
{
m_cListeningEvts.remove(strEvtName);
}
bool GitlModuleDelegate::detonate(QSharedPointer<GitlEvent> pcEvt )
{
QMap<QString, std::function<bool (GitlEvent&)>>::iterator p =
m_cListeningEvts.find(pcEvt->getEvtName());
if( p != m_cListeningEvts.end() )
{
(p.value())(*pcEvt.data());
}
return true;
}
bool GitlModuleDelegate::xIsListenToEvt( const QString& strEvtName )
{
return m_cListeningEvts.contains(strEvtName);
}
void GitlModuleDelegate::dispatchEvt( const GitlEvent& rcEvt ) const
{
if(m_pcGitlEvtBus != NULL)
m_pcGitlEvtBus->post(rcEvt);
}
void GitlModuleDelegate::detach()
{
if(m_pcGitlEvtBus != NULL)
m_pcGitlEvtBus->unregisterModule(this);
m_pcGitlEvtBus = NULL;
}
void GitlModuleDelegate::attach(GitlEventBus *pcEventBus)
{
if(pcEventBus == NULL)
return;
detach();
m_pcGitlEvtBus = pcEventBus;
m_pcGitlEvtBus->registerModule(this);
}
TestCase
#include <QCoreApplication>
#include <iostream>
#include <QtTest/QtTest>
#include <QTest>
#include <QSharedPointer>
#include <QString>
#include <functional>
#include "gitldef.h"
#include "gitlmodule.h"
#include "gitleventbus.h"
using namespace std;
/// test event bus
class TestModule : public GitlModule //继承自GitlModule
{
public:
TestModule(GitlEventBus* pcEventBus = NULL):
GitlModule(pcEventBus)
{
this->m_bNotified = false;
}
void subscribeInsideClass()
{
subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK(TestModule::callback)); //绑定内部的callback函数
}
bool callback( GitlEvent& rcEvt)
{
Q_UNUSED(rcEvt)
this->m_bNotified = true;
return true;
}
ADD_CLASS_FIELD(bool, bNotified, getNotified, setNotified)
};
/// custom event, 用户自定义事件,集成GitlEvent
class CustomEvent : public GitlEvent
{
CLONABLE(CustomEvent)
public:
CustomEvent( const QString& strEvtName ) : GitlEvent(strEvtName)
{
m_strCustomVar = "Custom String"; //自定义私有成员
}
ADD_CLASS_FIELD(QString, strCustomVar, getCustomVar, setCustomVar)
};
/// 用户自定义事件监听模块,继承自GitlModule
class CustomEventListener : public GitlModule
{
public:
CustomEventListener()
{
this->m_bNotified = false;
}
bool callback( GitlEvent& rcEvt)
{
CustomEvent& pcCusEvt = static_cast<CustomEvent&>(rcEvt);
this->m_bNotified = true;
this->m_strCustomVar = pcCusEvt.getCustomVar();
return true;
}
ADD_CLASS_FIELD(bool, bNotified, getNotified, setNotified)
ADD_CLASS_FIELD(QString, strCustomVar, getCustomVar, setCustomVar)
};
/// test case
class TestCase : public QObject
{
Q_OBJECT
private slots:
void lamdaListening()
{
TestModule cModule;
cModule.subscribeToEvtByName("TEST_EVENT_1",
[&](GitlEvent& e)->bool
{
Q_UNUSED(e)
cModule.setNotified(true);
return true;
});
QVERIFY(!cModule.getNotified()); //QVERIFY(condition)
GitlEvent cEvt("TEST_EVENT_1");
cModule.dispatchEvt(cEvt);
QVERIFY(cModule.getNotified());
}
void listenInsideClass()
{
TestModule cModule;
cModule.subscribeInsideClass();
QVERIFY(!cModule.getNotified());
GitlEvent cEvt("TEST_EVENT_1");
cEvt.dispatch();
QVERIFY(cModule.getNotified());
}
void listenOutsideClass()
{
TestModule cModule;
cModule.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule, TestModule::callback));
QVERIFY(!cModule.getNotified());
GitlEvent cEvt("TEST_EVENT_1");
cModule.dispatchEvt(cEvt);
QVERIFY(cModule.getNotified());
}
void unsubscribe()
{
TestModule cModule;
cModule.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule, TestModule::callback));
cModule.unsubscribeToEvtByName("TEST_EVENT_1");
QVERIFY(!cModule.getNotified());
GitlEvent cEvt("TEST_EVENT_1");
cModule.dispatchEvt(cEvt);
QVERIFY(!cModule.getNotified());
}
/// 一对多,一个事件,多个Module关注
void oneToMany()
{
TestModule cSender;
TestModule cModule1;
TestModule cModule2;
TestModule cModule3;
cModule1.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule1, TestModule::callback));
cModule2.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule2, TestModule::callback));
cModule3.subscribeToEvtByName("TEST_EVENT_2", MAKE_CALLBACK_OBJ(cModule3, TestModule::callback));
GitlEvent cEvt1("TEST_EVENT_1");
cSender.dispatchEvt(cEvt1); //某个Module 派发Event, 而其他Module 关注该Event的后进行处理
QVERIFY(cModule1.getNotified());
QVERIFY(cModule2.getNotified());
QVERIFY(!cModule3.getNotified());
GitlEvent cEvt2("TEST_EVENT_2");
cSender.dispatchEvt(cEvt2);
QVERIFY(cModule3.getNotified());
}
/// 用户自定义事件测试
void customEventTest()
{
CustomEventListener cModule;
cModule.subscribeToEvtByName("TEST_EVENT_1",MAKE_CALLBACK_OBJ(cModule, CustomEventListener::callback));
CustomEvent cEvt("TEST_EVENT_1");
cEvt.dispatch();
qDebug()<<__FUNCTION__<<cModule.getNotified();
qDebug()<<__FUNCTION__<<cModule.getCustomVar();
QVERIFY(cModule.getNotified());
QVERIFY(cModule.getCustomVar() == QString("Custom String")); //用户自定义的参数, 需要关注CModule的getCustomVar
}
///多个EventBus和多个Module对象
void multiplyEventBus()
{
GitlEventBus* pcBus1 = GitlEventBus::create(); TestModule cModule1(pcBus1); TestModule cModule2(pcBus1);
GitlEventBus* pcBus2 = GitlEventBus::create(); TestModule cModule3(pcBus2); TestModule cModule4(pcBus2);
/// all module are listening to the same events, but on different event buses.
cModule1.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule1, TestModule::callback));
cModule2.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule2, TestModule::callback));
cModule3.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule3, TestModule::callback));
cModule4.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule4, TestModule::callback));
/// event
CustomEvent cEvt("TEST_EVENT_1");
/// no one get notified because no module is attached to the default event bus
cEvt.dispatch();
QVERIFY(!cModule1.getNotified());
QVERIFY(!cModule2.getNotified());
QVERIFY(!cModule3.getNotified());
QVERIFY(!cModule4.getNotified());
/// this will only notify module 1 & 2
cEvt.dispatch(pcBus1);
QVERIFY(cModule1.getNotified());
QVERIFY(cModule2.getNotified());
QVERIFY(!cModule3.getNotified());
QVERIFY(!cModule4.getNotified());
/// this will notify module 3 & 4
cEvt.dispatch(cModule3.getEventBus());
QVERIFY(cModule3.getNotified());
QVERIFY(cModule4.getNotified());
/// make sure everyone is attached to the correct event bus
QVERIFY(cModule1.getEventBus() == pcBus1);
QVERIFY(cModule2.getEventBus() == pcBus1);
QVERIFY(cModule3.getEventBus() == pcBus2);
QVERIFY(cModule4.getEventBus() == pcBus2);
/// create cModule5
TestModule cModule5(pcBus1);
cModule5.subscribeToEvtByName("TEST_EVENT_1", MAKE_CALLBACK_OBJ(cModule5, TestModule::callback));
cEvt.dispatch(pcBus2);
QVERIFY(!cModule5.getNotified());
cModule5.attach(pcBus2);
cEvt.dispatch(pcBus2);
QVERIFY(cModule5.getNotified());
}
};
/// test main
QTEST_MAIN(TestCase)
#include "testcase.moc"
- 运行测试结果
********* Start testing of TestCase *********
Config: Using QtTest library 5.14.2, Qt 5.14.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 5.3.1 20160406 (Red Hat 5.3.1-6))
PASS : TestCase::initTestCase()
PASS : TestCase::lamdaListening()
PASS : TestCase::listenInsideClass()
PASS : TestCase::listenOutsideClass()
PASS : TestCase::unsubscribe()
PASS : TestCase::oneToMany()
QDEBUG : TestCase::customEventTest() customEventTest true
QDEBUG : TestCase::customEventTest() customEventTest "Custom String"
PASS : TestCase::customEventTest()
PASS : TestCase::multiplyEventBus()
PASS : TestCase::cleanupTestCase()
Totals: 9 passed, 0 failed, 0 skipped, 0 blacklisted, 1ms
********* Finished testing of TestCase *********