Qt源码中的设计模式:绘图系统与桥接模式

桥接模式

桥接模式(Bridge Pattern)是一种用于将抽象部分和实现部分分离的设计模式。它通过将实现部分抽象化,使得抽象部分和实现部分可以独立地变化,从而实现系统的松耦合和可扩展性。

在桥接模式中,抽象部分和实现部分分别由两个抽象类或接口来定义,并通过一个桥接接口来将它们连接起来。这样,在系统需要增加新的抽象部分或实现部分时,只需要扩展抽象类或实现类,而不需要修改原有的代码,从而实现系统的可扩展性和灵活性。

桥接模式UML类图

上面两段话是不是非常令人困惑?没关系,GoF原本的描述就是这样的,这也让我很迷惑。不用担心,接下来我会努力用人话以及程序实例说明桥接模式的意义所在。我个人的理解是,如UML类图所示,Abstraction类和Implmentor类是组合关系(Abstraction has a Implmentor),表现为Abstraction类可能拥有一个Implmentor类的成员变量。Implmentor类作为Abstraction类的一种属性存在,而桥接模式则通过一种扩展性更好的方式,形成了一种类之间组合关系的结构。

举个例子,假设有一个图形库,其中包含多种不同的图形(如圆形、矩形等)和多种不同的颜色(如红色、蓝色等)。如果使用桥接模式,可以将图形和颜色分别抽象化,分别由两个抽象类定义,并通过一个桥接接口将它们连接起来。这样,在需要增加新的图形或颜色时,只需要扩展对应的抽象类或实现类,而不需要修改原有的代码,从而实现系统的可扩展性和灵活性。

#include <iostream>

// 颜色接口,可以对应UML中的Implmentor类
class Color {
public:
    virtual void draw() = 0;
};

// 红色实现类,可以对应UML中的ConcreteImplmentorA类
class RedColor : public Color {
public:
    void draw() {
        std::cout << "draw in red color." << std::endl;
    }
};

// 蓝色实现类,可以对应UML中的ConcreteImplmentorB类
class BlueColor : public Color {
public:
    void draw() {
        std::cout << "draw in blue color." << std::endl;
    }
};

// 图形接口,可以对应UML中的Abstraction类
class Shape {
protected:
    Color* color_;
public:
    Shape(Color* color) : color_(color) {}
    virtual void draw() = 0;
};

// 圆形类,可以对应UML中的RefineAbstraction类
class Circle : public Shape {
public:
    Circle(Color* color) : Shape(color) {}
    void draw() {
        std::cout << "draw a circle, ";
        color_->draw();
    }
};

// 矩形类,可以对应UML中的RefineAbstraction类
class Rectangle : public Shape {
public:
    Rectangle(Color* color) : Shape(color) {}
    void draw() {
        std::cout << "draw a rectangle, ";
        color_->draw();
    }
};

int main() {
    // 创建红色实现类和蓝色实现类
    Color* red = new RedColor();
    Color* blue = new BlueColor();

    // 创建圆形和矩形,并设置颜色
    Shape* circle = new Circle(red);
    Shape* rectangle = new Rectangle(blue);

    // 绘制图形
    circle->draw();
    rectangle->draw();

    // 释放内存
    delete red;
    delete blue;
    delete circle;
    delete rectangle;

    return 0;
}

在上面的示例中,我们定义了一个颜色接口和两个颜色实现类(红色实现类和蓝色实现类),以及一个图形接口和两个图形类(圆形类和矩形类)。在图形类中,我们使用了一个颜色对象来表示图形的颜色,从而将抽象部分和实现部分分离。在main函数中,我们创建了红色和蓝色实现类,以及圆形和矩形对象,并设置它们的颜色。最后,我们调用它们的绘制方法来绘制图形。在程序结束时,我们释放了内存(最好养成习惯)。

绘图系统中的桥接模式

在Qt的绘图系统中,QPaintDeviceQWidgetQPaintEngineQRasterPaintEngine这些类的角色和关系,可以用桥接模式来描述。在这个模型中:

  • QPaintDevice可以看作是"抽象部分"(Abstraction)。它定义了一些公共接口,比如width(), height(), logicalDpiX()等,这些接口用于描述一个绘图设备的基本属性。值得注意的是,QPaintDevice有一个paintEngine方法,返回一个QPaintEngine指针。

  • QWidgetQPaintDevice的一个具体子类,它代表了一个可视的窗口。这里,QWidget就是"具体的抽象部分"(RefineAbstraction)。QWidget重写了QPaintDevicepaintEngine方法,并返回QRasterPaintEngine指针

  • QPaintEngine可以看作是"实现部分"(Implmentor)。它定义了一些低级别的绘图接口,比如drawPath(), drawImage()等,这些接口用于在具体的绘图设备上进行绘制。

  • QRasterPaintEngineQPaintEngine的一个具体子类,它实现了在基于光栅的设备上进行绘制。这里,QRasterPaintEngine就是"具体的实现部分"(ConcreteImplmentor)。

这样一来,QPaintDeviceQPaintEngine就形成了一个桥接,使得绘图设备的抽象(比如QWidget)和具体的绘制操作(比如在光栅设备上绘制)能够独立地变化。

在这个桥接模式中,QPainter就扮演了一个"客户端"(Client)的角色(没有在UML类图中画出来,但会在后面的代码示例中体现),它使用QPaintDevice提供的抽象接口,并通过QPaintEngine来进行具体的绘制操作。

可以使用C++程序来描述这几个Qt类的关系。请注意,这只是笔者根据自己对Qt源代码的理解,进行的简单抽象,实际代码与程序实例有一定差异,省略了很多的细节。但用于描述Qt绘图系统如何使用桥接模式,应该是勉强足够的。

class QPaintDevice {
public:
    virtual QPaintEngine* paintEngine() const = 0;  // 纯虚函数
    // 其他方法...
};

class QWidget : public QPaintDevice {
public:
    QPaintEngine* paintEngine() const override {
        // 返回一个QRasterPaintEngine实例
        static QRasterPaintEngine instance;
        return &instance;
    }
    // 其他方法...
};

class QPaintEngine {
public:
    virtual void drawPath(const QPainterPath &path) = 0;  // 纯虚函数
    // 其他方法...
};

class QRasterPaintEngine : public QPaintEngine {
public:
    void drawPath(const QPainterPath &path) override {
        // 在光栅设备上绘制路径
        // 具体实现...
    }
    // 其他方法...
};

class QPainter {
public:
    QPainter(QPaintDevice *device) : device(device), engine(device->paintEngine()) {}
    void drawPath(const QPainterPath &path) {
        engine->drawPath(path);
    }
private:
    QPaintDevice *device;
    QPaintEngine *engine;
};

上面的代码示例中,QPainter是客户端代码,它接受一个QPaintDevice对象,并通过QPaintDevicepaintEngine方法获取对应的QPaintEngine对象。然后在需要绘图时,QPainter会通过QPaintEnginedrawPath方法进行绘制。这样,QPainter就能够在不同的设备上进行绘制,而不需要关心具体的绘制过程是如何实现的。这桥接模式的设计思想:将抽象(QPaintDevice)和实现(QPaintEngine)分离,使得二者可以独立地变化

总结

可以看到,桥接模式很好地体现了依赖倒转原则(Dependency Inversion Principle,DIP)。依赖倒转原则是面向对象设计中的一个重要原则,它要求高层模块不应该依赖低层模块,而是应该依赖于抽象。而桥接模式正是通过将实现部分抽象化,使得抽象部分和实现部分可以独立地变化,从而实现了高层模块对低层模块的解耦,符合了依赖倒转原则。

具体来说,在桥接模式中,抽象部分和实现部分分别由两个抽象类或接口来定义,并通过一个桥接接口来将它们连接起来。高层模块只需要依赖于抽象类或接口,而不需要依赖于具体的实现类,从而实现了依赖倒转。

最后,需要强调,尽管文章大致阐述了Qt绘图系统如何使用桥接模式,但仍强烈建议读者通过对Qt源代码进行调试。实际调试才能完全理解Qt源代码的设计思想,以及Qt设计者如何活用设计模式,学到精髓。

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

推荐阅读更多精彩内容