Qt源码中的设计模式:事件传播机制与责任链模式

责任链模式

责任链模式是一种行为型设计模式,它将请求和处理请求的对象解耦,形成一个请求处理链。在该模式中,多个对象依次处理同一个请求,直到找到一个能够处理该请求的对象为止。

责任链模式的主要作用是解决多个对象处理同一个请求时的耦合问题。在传统的设计中,如果要处理一个请求,通常需要在代码中明确指定请求由哪个对象来处理。这样,请求和处理请求的对象就存在了一定的耦合关系,这会导致系统的可扩展性和可维护性变得很差。而责任链模式则通过将请求和处理请求的对象解耦,使得系统更加灵活和可扩展。

在责任链模式中,每个对象都有一个指向下一个对象的引用,形成了一个链式结构。当一个请求发生时,它会被依次传递给链上的每个对象,直到找到一个能够处理该请求的对象为止。每个对象都可以根据自己的状态和职责决定是否处理该请求,从而实现请求的处理与请求的发起者解耦。

责任链模式UML类图

在实际应用中,责任链模式有很多的使用场景。比如,在一个大型GUI应用程序中,有很多用户界面元素需要响应鼠标或键盘输入事件。如果每个用户界面元素都必须处理所有的输入事件,那么系统的性能将受到严重影响。使用责任链模式,可以将这些用户界面元素组织成一个责任链,每个用户界面元素只需要处理自己关心的输入事件,将不关心的事件交给下一个对象处理。这样可以显著提高系统的处理能力和吞吐量,同时也提高了代码的可扩展性和可维护性。

责任链模式还可以用来处理日志记录、权限控制、异常处理、消息传递等问题。在这些应用场景中,每个对象都可以根据自己的状态和职责决定是否处理该请求,从而实现请求的处理与请求的发起者解耦,提高了系统的灵活性和可扩展性。

下面给出责任链模式的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,如果没有任何处理器能够处理请求,则请求不会被处理。

总之,责任链模式是一种非常有用的设计模式,它可以将请求和处理请求的对象解耦,从而提高系统的灵活性和可扩展性。在实际应用中,它可以帮助我们解决很多复杂的问题,提高系统的处理能力和吞吐量,同时也提高了代码的可维护性和可读性。

Qt事件传播机制

在Qt中,事件传播机制是实现GUI交互的重要机制之一,而事件传播机制是基于对象树机制的。在之前的文章:Qt源码中的设计模式:对象树机制与组合模式 中说到,每个对象都有一个父对象和零个或多个子对象,构成了一个对象树。子对象的生命周期与父对象相关联。当一个父对象被删除时,它的所有子对象也会被自动删除。在事件传播过程中,Qt会按照对象树的结构依次将该事件传递给每个对象,直到找到一个能够处理该事件的对象为止。这种机制可以让Qt中的对象之间解耦,提高系统的灵活性和可扩展性。

需要注意的是,只有一些特定的事件会被传递,其他事件则不会被传递。鼠标事件、键盘事件、绘图事件、聚焦事件和拖放事件会被传递到对象的父对象和子对象。除了这些事件,Qt还提供了很多高级事件,如定时器事件、系统事件等,需要在对象中显式地处理。

事件传播机制可以很好地体现责任链模式。责任链模式是一种行为型设计模式,它将请求和处理请求的对象解耦,形成一个请求处理链。在该模式中,多个对象依次处理同一个请求,直到找到一个能够处理该请求的对象为止,这个过程就是责任链模式中的请求处理链。不过可能会有读者疑惑:既然对象树机制是组合模式的一种实现,而事件传播机制又体现出责任链模式,这是否有冲突。实际上,设计模式在实际工程中经常是有变体的,也会结合起来使用。在父子对象的内存管理问题上,更多的展现出组合模式的思想;在事件的传播上,则更多体现出责任链模式的思想。这是因为,一些事件可以被对象树上某一个节点处理掉,其他节点不需要关注;而父子节点的销毁工作,则是整个链条上所有节点都需要处理的事情。读者需要结合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();
    }
};

通过上面的示例,可以看到,QObjectQWidgetQEvent三个类的设计和实现,体现了责任链模式。作为所有Qt对象的基类,QObject通过提供event()eventFilter()两个虚函数,构建了事件处理的基础。其中event()负责处理自身接收到的事件,eventFilter()则处理需要过滤的事件。如果事件未被处理,它会被传递给父对象,形成责任链。

QWidget,继承自QObject,重写了event()方法,专门处理用户界面事件。例如,当按键事件发生,我们可以获取按下的键码,然后调用事件的accept()方法表示处理完成,从而阻止事件继续向上传递。如果QWidget无法处理事件,它会将事件传递给父对象。

QEvent是所有事件的基类,每个事件有独特的类型标识符。QEvent类还提供了isAccepted()accept()ignore()方法,标记事件是否被接受和处理。一旦事件被处理,我们可以通过调用accept()方法停止事件的传递,避免重复处理。

因此,QObject作为事件处理接口,QWidget实现具体的处理逻辑,而QEvent则扮演了请求的角色。这三个类的协作为Qt提供了一种高效、灵活且可扩展的事件处理机制,帮助开发者轻松处理各种事件和场景。

总结

至此,我们已经解析了Qt的对象树机制,构建在对象树机制之上的事件传递机制,以及它们背后的设计思想。在后续的文章中,我们可能还会继续解析Qt源码中事件机制相关的设计模式。事件机制确实是Qt中的核心机制之一,我们能够深挖的东西,恐怕还有很多。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容