1、基本概念
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,将请求沿着处理链传递,直到有一个对象能够处理为止。
2、实现的模块有:
Handler(处理者):定义一个处理请求的接口。
ConcreteHandler(具体处理者):实现了处理者接口,判断自己是否能够处理请求,如果不能将请求传递给下一个处理者。
Request(请求):封装了请求的信息,通常作为处理者方法的参数传递。
3、使用场景
当需要将请求的发送者和接收者进行解耦时。
当有多个对象可以处理同一请求,但不确定哪个对象应该处理时。
当需要动态指定处理请求的顺序时。
4、责任链模式的C++实现:
#include <iostream>
#include <memory>
#include <string>
// 抽象处理器类
class Handler {
public:
virtual ~Handler() {}
// 处理请求的方法
virtual void HandleRequest(const std::string& request) {
if (successor_) {
successor_->HandleRequest(request);
}
}
// 设置后继处理器
void setSuccessor(std::shared_ptr<Handler> successor) {
successor_ = successor;
}
protected:
std::shared_ptr<Handler> successor_;
};
// 具体处理器类A
class ConcreteHandlerA : public Handler {
public:
void HandleRequest(const std::string& request) override {
if (request == "A") {
std::cout << "ConcreteHandlerA handles the request: " << request << std::endl;
} else {
Handler::HandleRequest(request);
}
}
};
// 具体处理器类B
class ConcreteHandlerB : public Handler {
public:
void HandleRequest(const std::string& request) override {
if (request == "B") {
std::cout << "ConcreteHandlerB handles the request: " << request << std::endl;
} else {
Handler::HandleRequest(request);
}
}
};
// 具体处理器类C
class ConcreteHandlerC : public Handler {
public:
void HandleRequest(const std::string& request) override {
if (request == "C") {
std::cout << "ConcreteHandlerC handles the request: " << request << std::endl;
} else {
Handler::HandleRequest(request);
}
}
};
int main() {
std::shared_ptr<Handler> handlerA = std::make_shared<ConcreteHandlerA>();
std::shared_ptr<Handler> handlerB = std::make_shared<ConcreteHandlerB>();
std::shared_ptr<Handler> handlerC = std::make_shared<ConcreteHandlerC>();
// 设置责任链
handlerA->setSuccessor(handlerB);
handlerB->setSuccessor(handlerC);
// 发送请求
handlerA->HandleRequest("A");
handlerA->HandleRequest("B");
handlerA->HandleRequest("C");
handlerA->HandleRequest("D");
return 0;
}
在上述示例中,Handler是抽象处理器类,定义了处理请求的方法。ConcreteHandlerA、ConcreteHandlerB和ConcreteHandlerC是具体处理器类,分别处理请求A、B和C。在创建这些处理器对象时,按照责任链的顺序将它们连接起来。在main函数中,程序创建了一个责任链,将请求依次发送给处理器A、B、C,如果没有任何处理器能够处理请求,则请求不会被处理。
总之,责任链模式是一种非常有用的设计模式,它可以将请求和处理请求的对象解耦,从而提高系统的灵活性和可扩展性。在实际应用中,它可以帮助我们解决很多复杂的问题,提高系统的处理能力和吞吐量,同时也提高了代码的可维护性和可读性。
5、Qt事件传播机制
QT源码:事件系统
QT的事件处理系统同样用到了事件处理系统,其中事件通过事件队列发送到对应的对象,每个对象都可以处理该事件,如果该对象无法处理,将会发给下一个对象。
以下是 QAppliaction 发送鼠标事件给 QWidget 的部分源码:
//接收鼠标事件的对象w
QWidget* w = static_cast<QWidget *>(receiver);
//鼠标事件e
QMouseEvent* mouse = static_cast<QMouseEvent*>(e);
......
while (w) {
//创建一个新的鼠标事件对象,用于在对象树中传播鼠标事件
QMouseEvent me(mouse->type(), relpos, mouse->windowPos(), mouse->globalPos(),
mouse->button(), mouse->buttons(), mouse->modifiers(), mouse->source());
......
//如果鼠标事件被接受,打破循环
eventAccepted = (w == receiver ? mouse : &me)->isAccepted();
if (res && eventAccepted)
break;
......
//如果鼠标事件未被接受,将w设置为w的父组件,继续循环
w = w->parentWidget();
}
可以看出, QApplication 将鼠标事件沿着对象树传递,直到有一个对象能够处理为止,符合责任链模式的思想,其中:
QObject: Handler(处理者),定义一个处理鼠标事件的接口。
QWidgt: ConcreteHandler(具体处理者),实现了 QObject 的接口,判断自己是否能够处理鼠标事件,如果不能将请求传递给父类 QWidget。
QMouseEvent: Request(请求),封装了鼠标事件的信息。
**QAppliaction:**客户端,是请求的发起者。
以上即可展现责任链模式在QT中应用。如果在意细节,可以看下方第6条**,否则,后面不用看!**
6、Qt事件传播机制 具体细节
事件传播机制和对象树机制共同构成了Qt中对象的一种管理方式和事件的一种传播方式。通过在对象之间建立处理请求的责任链,可以使请求的处理与请求的发起者解耦,提高代码的可扩展性和可维护性。另外,Qt还提供了QObject::installEventFilter
方法,即可以安装一个事件过滤器来处理事件。事件过滤器是一个单独的对象,它可以拦截并处理一个或多个对象的事件。因此,事件过滤器也可以看作责任链模式中的一环。
下面给出Qt源码中如何实现事件传播机制,并且体现出责任链模式的一个示例。注意,这个示例省略了比较多的细节,但作为示例去描述Qt源码中如何体现责任链模式,应该是勉强足够的。
#include <iostream>
enum EventType { UnknownEvent, MouseButtonPressEvent };
class QEvent {
public:
explicit QEvent(EventType type) : _type(type), _accepted(false) {}
virtual ~QEvent() {}
EventType type() const { return _type; }
void accept() { _accepted = true; }
void ignore() { _accepted = false; }
bool isAccepted() const { return _accepted; }
private:
EventType _type;
bool _accepted;
};
class QObject {
public:
virtual ~QObject() {}
virtual bool event(QEvent *event) {
// 默认实现,不处理任何事件
return false;
}
virtual bool eventFilter(QObject *, QEvent *) {
// 默认实现,不处理任何事件
return false;
}
};
class QWidget : public QObject {
public:
virtual ~QWidget() {}
bool event(QEvent *event) override {
switch (event->type()) {
case MouseButtonPressEvent:
mousePressEvent(event);
return true;
default:
return QObject::event(event);
}
}
virtual void mousePressEvent(QEvent *event) {
std::cout << "QWidget: Mouse button press event\n";
event->accept();
}
};
通过上面的示例,可以看到,QObject
,QWidget
和QEvent
三个类的设计和实现,体现了责任链模式。
QWidget
,继承自QObject
,重写了event()
方法,专门处理用户界面事件。例如,当按键事件发生,我们可以获取按下的键码,然后调用事件的accept()
方法表示处理完成,从而阻止事件继续向上传递。如果QWidget
无法处理事件,它会将事件传递给父对象。
QEvent
是所有事件的基类,每个事件有独特的类型标识符。QEvent
类还提供了isAccepted()
、accept()
和ignore()
方法,标记事件是否被接受和处理。一旦事件被处理,我们可以通过调用accept()
方法停止事件的传递,避免重复处理。
作为所有Qt对象的基类,QObject
通过提供event()
和eventFilter()
两个虚函数,构建了事件处理的基础。其中event()
负责处理自身接收到的事件,eventFilter()
则处理需要过滤的事件。如果事件未被处理,它会被传递给父对象,形成责任链。那么传递给父对象的逻辑,在哪里呢?往QWidget::event()
方法里打断点调试就会发现,调用栈会经过QApplication::notify(QObject *receiver, QEvent *e)
,也就是说,事件的分发回经过里面QApplication::notify
。切换到这个方法的调用点查看源代码,可以看到这么一段逻辑:
bool QApplication::notify(QObject* receiver, QEvent* e)
{
//其他逻辑
//...
bool res = false;
switch (e->type()) {
//处理其他事件类型
//...
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
{
QWidget* w = static_cast<QWidget*>(receiver);
QMouseEvent* mouse = static_cast<QMouseEvent*>(e);
QPoint relpos = mouse->pos();
if (e->spontaneous()) {
if (e->type() != QEvent::MouseMove)
QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, e, relpos);
// ### Qt 5 These dynamic tool tips should be an OPT-IN feature. Some platforms
// like OS X (probably others too), can optimize their views by not
// dispatching mouse move events. We have attributes to control hover,
// and mouse tracking, but as long as we are deciding to implement this
// feature without choice of opting-in or out, you ALWAYS have to have
// tracking enabled. Therefore, the other properties give a false sense of
// performance enhancement.
if (e->type() == QEvent::MouseMove && mouse->buttons() == 0
&& w->rect().contains(relpos)) { // Outside due to mouse grab?
d->toolTipWidget = w;
d->toolTipPos = relpos;
d->toolTipGlobalPos = mouse->globalPos();
QStyle* s = d->toolTipWidget->style();
int wakeDelay = s->styleHint(QStyle::SH_ToolTip_WakeUpDelay, 0, d->toolTipWidget, 0);
d->toolTipWakeUp.start(d->toolTipFallAsleep.isActive() ? 20 : wakeDelay, this);
}
}
bool eventAccepted = mouse->isAccepted();
QPointer<QWidget> pw = w;
while (w) {
QMouseEvent me(mouse->type(), relpos, mouse->windowPos(), mouse->globalPos(),
mouse->button(), mouse->buttons(), mouse->modifiers(), mouse->source());
me.spont = mouse->spontaneous();
me.setTimestamp(mouse->timestamp());
QGuiApplicationPrivate::setMouseEventFlags(&me, mouse->flags());
// throw away any mouse-tracking-only mouse events
if (!w->hasMouseTracking()
&& mouse->type() == QEvent::MouseMove && mouse->buttons() == 0) {
// but still send them through all application event filters (normally done by notify_helper)
d->sendThroughApplicationEventFilters(w, w == receiver ? mouse : &me);
res = true;
}
else {
w->setAttribute(Qt::WA_NoMouseReplay, false);
res = d->notify_helper(w, w == receiver ? mouse : &me);
e->spont = false;
}
eventAccepted = (w == receiver ? mouse : &me)->isAccepted();
if (res && eventAccepted)
break;
if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))
break;
relpos += w->pos();
w = w->parentWidget();
}
mouse->setAccepted(eventAccepted);
if (e->type() == QEvent::MouseMove) {
if (!pw)
break;
w = static_cast<QWidget*>(receiver);
relpos = mouse->pos();
QPoint diff = relpos - w->mapFromGlobal(d->hoverGlobalPos);
while (w) {
if (w->testAttribute(Qt::WA_Hover) &&
(!QApplication::activePopupWidget() || QApplication::activePopupWidget() == w->window())) {
QHoverEvent he(QEvent::HoverMove, relpos, relpos - diff, mouse->modifiers());
d->notify_helper(w, &he);
}
if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))
break;
relpos += w->pos();
w = w->parentWidget();
}
}
d->hoverGlobalPos = mouse->globalPos();
}
break;
//处理其他事件类型
//...
}
return res;
}
我们在这里只关心鼠标事件(QEvent::MouseButtonPress
,QEvent::MouseButtonRelease
,QEvent::MouseButtonDblClick
,QEvent::MouseMove:
)的处理。下面分析一下关键的程序段:
-
在处理鼠标事件时,首先获取当前
QWidget
对象w
。QWidget* w = static_cast<QWidget *>(receiver);
-
把事件循环遍历至最高级父窗口或直到一个处理了事件的窗口。在此过程中,对于没有成功处理鼠标事件的窗口,事件会沿着窗口的父链继续传递。
while (w) {
// 将事件发送给QWidget对象w,尝试让其处理
// ...// 当事件已经被接收并得到处理,跳出循环 if (res && eventAccepted) break; // 当到达顶层窗口或当前窗口标记为Qt::WA_NoMousePropagation (无鼠标事件传递)时,跳出循环 if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation)) break; // 获取父窗口,在while循环下一轮尝试让父窗口去处理 w = w->parentWidget();
}
在这个while循环中,就会不断地去获取父对象,尝试让其处理。事件得到处理,或者当到达顶层窗口,或者当前窗口标记为Qt::WA_NoMousePropagation
(无鼠标事件传递)时,跳出循环。这里就体现出了事件处理的传递链。
可以继续解析上面的程序示例中,"将事件发送给QWidget对
象w,尝试让其处理"部分没有展示出的代码:
-
根据收到的原始鼠标事件(
mouse
)创建一个新的鼠标事件(me
),并使用正确的相对位置(relpos
)更新它。同时设置一些其他属性,如是否自发、时间戳和鼠标事件标志。QMouseEvent me(mouse->type(), relpos, mouse->windowPos(), mouse->globalPos(),
mouse->button(), mouse->buttons(), mouse->modifiers(), mouse->source());
me.spont = mouse->spontaneous();
me.setTimestamp(mouse->timestamp());
QGuiApplicationPrivate::setMouseEventFlags(&me, mouse->flags()); -
对于没有鼠标跟踪并且是无按钮按下的鼠标移动事件(仅用于鼠标跟踪的事件),忽略这个事件,并将其传递给所有应用程序事件过滤器。设置结果变量
res = true
表示事件已被处理。if (!w->hasMouseTracking()
&& mouse->type() == QEvent::MouseMove && mouse->buttons() == 0) {
// but still send them through all application event filters (normally done by notify_helper)
d->sendThroughApplicationEventFilters(w, w == receiver ? mouse : &me);
res = true;
} -
否则,尝试将
mouse
或me
事件传递给QWidget
对象(视情况而定)。更新事件的自发属性为false
。else {
w->setAttribute(Qt::WA_NoMouseReplay, false);
res = d->notify_helper(w, w == receiver ? mouse : &me);
e->spont = false;
} -
检查事件是否已被接受。这个步骤会影响后续窗口的事件传递。
eventAccepted = (w == receiver ? mouse : &me)->isAccepted();
综上,QObject
作为事件处理接口,QWidget
实现具体的处理逻辑,而QEvent
则扮演了请求的角色,QApplication
参与了事件的分发和向上传递。这几个类的协作为Qt提供了一种高效、灵活且可扩展的事件处理机制,帮助开发者轻松处理各种事件和场景。
总结
至此,我们已经解析了Qt的对象树机制,构建在对象树机制之上的事件传递机制,以及它们背后的设计思想。在后续的文章中,我们可能还会继续解析Qt源码中事件机制相关的设计模式。事件机制确实是Qt中的核心机制之一,我们能够深挖的东西,恐怕还有很多。
原文链接:https://blog.csdn.net/buhuiCyvyan/article/details/138793057