《C++20设计模式》---桥接模式学习笔记

c++20设计模式

  • [第 7 章 桥接模式](#第 7 章 桥接模式)
    • [7.1 Pimpl模式](#7.1 Pimpl模式)
    • [7.2 桥接模式介绍](#7.2 桥接模式介绍)
    • [7.3 总结](#7.3 总结)
    • [7.4 代码](#7.4 代码)

第 7 章 桥接模式

如果你一直关注C++编译器(特别GCC, Clang和MSVC)的最新进展,那么可能已经注意到编译速度提高了。特别是,编译的体量越来越大时。因为,编译器实际上只会重新编译改动的代码,并复用已编译好的未改动的部分,而不是重新构建整个编译单元。

之所以提起C++编译器,是因为过去开发者们一直在使用一个奇怪的技巧来加快编译速度。

7.1 Pimpl模式

首先,我们从技术角度来解释下Pimpl模式。假如我们要定义一个Person类。其中保存了人物的名字,并且提供了打印问候语的方法。除了像平时那样定义Person的成员,我们还可以像下面这样定义类:

cpp 复制代码
struct Person {
    std::string name;
    void greet();

    Person();
    ~Person();

    class PersonImpl;
    PersonImpl *impl;
};

看起来很简单的类尽然搞出如此多的工作!我们仔细看看:Person中定义了namec成员和greet()方法,但为什么要定义构造函数和西沟函数呢?PersonImpl类又是干什么的?

我们看到的这个Person类将其具体实现隐藏在另一个类中,即PersonImpl。需要格外注意的是,PersonImpl类并不是在头文件中定义的,而是驻留在 .cpp 文件(person.cpp,因此Person和PersonImpl放在一起)。它的定义非常简单:

cpp 复制代码
struct Person::PersonImpl {
    void greet(Person *p);
};

类Person中对PersonImpl做了前向声明,并且保存了一个PersonImpl类型指针。我们在Person的构造函数中初始化这个指针,并且在析构函数中销毁它。如果我们习惯使用智能指针,也可以将其替换为智能指针。

cpp 复制代码
Person::Person() : impl(new PersonImpl()) {

}

Person::~Person() {
    delete impl;
}

现在,我们要实现Person::greet()接口,正如你预料的那样,这个接口只是把控制权交给PersonImpl::greet():

cpp 复制代码
void Person::greet() {
    impl->greet(this);
}

void Person::PersonImpl::greet(Person *p) {
    printf("hello %s!\n", p->name.c_str());
}

简单来说,这就是Pimpl模式。因此,现在唯一的问题是:为什么?为什么要费尽周折实现greet()的代理并且传递this指针呢?这种方法有三个优点:

  • 这种方法隐藏了类的大部分实现。如果Person类有许多私有 / 共有成员,即使由于private / protected访问限定符的存在,客户不能直接访问这些成员,但我们也会一系列丰富的API,由此暴露了Person类内部的某些成员。如果是Pimpl模式,那么只需要对外提供公共接口即可。
  • 修改隐藏的Impl类的数据成员不会影响二进制文件的兼容性。
  • 头文件只需包含声明所需的头文件,而不必包含实现所需的头文件。例如,如果Person.h头文件Person类有一个vector<string>类型私有成员,则必须在Person.h头文件中同时包含<vector>和<string>(这是可传递的,所以任何使用Person.h的文件都包含它们)。使用Pimpl模式,这可以在 .cpp 文件中完成。

Pimlpl莫斯可以使系统中的头文件更加整洁,并且不必频繁改动。不过,副作用是会影响编译速度。就本章介绍的内容而言,Pimpl模式是桥接模式的一种很好的体现:在刚才的示例中,Pimpl是一种不透明指针(即我们不知道它背后是什么),它起着桥梁的作用,将公共接口的成员与其隐藏在 .cpp文件中的底层实现结合起来。


7.2 桥接模式介绍

Pimpl模式是桥接(Bridge)模式的一种具体实现,现在我们看看关于桥接模式更加通用的做法。假设有两种(数学意义上的)对象:几何对象以及将几何对象绘制在屏幕上的渲染器对象。

如同我们在适配器模式中展示的那样,假设我们可以以向量和光栅形式进行渲染(尽管我们不会在这里编写任何实际的绘图代码),并且将几何对象的形状限制为圆形。

首先, 基类Render定义如下:

cpp 复制代码
struct Render {
    virtual void render_circle(float x, float y, float radius) = 0;
};

我们可以轻松的构建向量渲染和光栅渲染的具体实现,下面将编写一些打印到控制台的代码来模拟实际渲染过程:

cpp 复制代码
struct VectorRender: Render
{
    void render_circle(float x, float y, float radius) override {
        std::cout << "\nDrawing a vector circle of radius " << radius << "\n";
    }
};

struct RasterRender:Render
{
    void render_circle(float x, float y, float radius) override {
        std::cout << "\nRasterizing circle of radius " << radius << "\n";
    }
};

几何对象的基类Shape可以保存一个渲染器的引用,我们在Shape中定义draw()个resize()两个成员函数用于支持渲染和调整尺寸的操作:

cpp 复制代码
struct Shape
{
protected:
    Render& render;
    Shape(Render& render) : render(render) {}
public:
    virtual void draw() = 0;
    virtual void resize(float factor) = 0;
};

可以看到,Shape类含有一个Render类型的引用。这正是桥接模型的"桥"之所在。接下来,我们创建Shape类的一个具体实现,并添加诸如圆心位置和半径等更多信息。

cpp 复制代码
struct Circle: Shape
{
    float x;
    float y;
    float radius;

    Circle(Render& render, float x, float y, float radius)
        :Shape(render), x(x), y(y), radius(radius) {}
    
    void draw() override {
        render.render_circle(x, y, radius);
    }

    void resize(float factor) override {
        radius *= factor;
    }
};

桥接模式很快就展现在眼前!当然,最有趣的部分在于函数draw():这是链接Circle(包含位置和大小信息)和渲染过程的桥梁。这里的桥就是渲染器,例如:

cpp 复制代码
void testBridgePattern()
 {
    RasterRender rr;
    Circle raster_cicle{rr, 5, 5, 5.5};
    raster_cicle.draw();

    raster_cicle.resize(2);
    raster_cicle.draw();
 }

在这段代码中,桥就是RasterRender:我们声明了一个RasterRenderer对象,并将其引用传递给Circle。此后,对函数draw()的调用将会以此RasterRenderer引用为桥梁来对Circle进行渲染。如果需要调整圆的大小,则可以调用resize(),渲染过程仍旧可以正常运行,因为渲染器并不知道也不关心其所渲染的Circle对象。
<<abstract>> Shape # renderer: Renderer& #Shape(Renderer&) +draw() +resize(float) <<abstract>> Renderer +render_circle(float) +render_square(float) Circle - radius : float +Circle() +draw() +resize(float) VectorRenderer +render_circle(float) +render_square(float) RasterRenderer +render_circle(float) +render_square(float)


7.3 总结

桥接模式的概念很简单,它通常作为连接器或黏合剂,将两个"不相关"的组件连接起来。抽象(接口)的使用允许组件之间在不了解具体实现的情况下彼此交互。

也就是说,桥接模式的参与者确实需要意识到彼此的存在。具体来说。Circle类需要引用Renderer; Renderer也需要知道如何绘制圆(即Renderer类的成员函数draw_circle的命名由来)。这与中介者模式形成了对比,中介者模式允许对象在毫不知晓对方的情况下进行通信。


7.4 代码

cpp 复制代码
#include<iostream>
#include<string>

struct Person {
    std::string name;
    void greet();

    Person();
    ~Person();

    class PersonImpl;
    PersonImpl *impl;
};

struct Person::PersonImpl {
    void greet(Person *p);
};

Person::Person() : impl(new PersonImpl()) {

}

Person::~Person() {
    delete impl;
}

void Person::greet() {
    impl->greet(this);
}

void Person::PersonImpl::greet(Person *p) {
    printf("hello %s!\n", p->name.c_str());
}


void testPimpl() {
    Person p;
    p.name = "Sarry";
    p.greet();
}

struct Render {
    virtual void render_circle(float x, float y, float radius) = 0;
};

struct VectorRender: Render
{
    void render_circle(float x, float y, float radius) override {
        std::cout << "\nDrawing a vector circle of radius " << radius << "\n";
    }
};

struct RasterRender:Render
{
    void render_circle(float x, float y, float radius) override {
       std::cout << "\nRasterizing circle of radius " << radius << "\n";
    }
};

struct Shape
{
protected:
    Render& render;
    Shape(Render& render) : render(render) {}
public:
    virtual void draw() = 0;
    virtual void resize(float factor) = 0;
};

struct Circle: Shape
{
    float x;
    float y;
    float radius;

    Circle(Render& render, float x, float y, float radius)
        :Shape(render), x(x), y(y), radius(radius) {}
    
    void draw() override {
        render.render_circle(x, y, radius);
    }

    void resize(float factor) override {
        radius *= factor;
    }
};

void testBridgePattern()
 {
    RasterRender rr;
    Circle raster_cicle{rr, 5, 5, 5.5};
    raster_cicle.draw();

    raster_cicle.resize(2);
    raster_cicle.draw();
 }

int main()
{
    testPimpl();
    testBridgePattern();
    return 0;
}
相关推荐
charlie1145141911 小时前
C++ STL CookBook
开发语言·c++·stl·c++20
大圣数据星球4 小时前
Fluss 写入数据湖实战
大数据·设计模式·flink
思忖小下5 小时前
梳理你的思路(从OOP到架构设计)_设计模式Template Method模式
设计模式·模板方法模式·eit
思忖小下16 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
liyinuo201718 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
aaasssdddd9620 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
T1an-120 小时前
设计模式之【观察者模式】
观察者模式·设计模式
思忖小下1 天前
梳理你的思路(从OOP到架构设计)_设计模式Factory Method模式
设计模式·工厂方法模式·eit
霁月风1 天前
设计模式——工厂方法模式
c++·设计模式·工厂方法模式
发飙的蜗牛'1 天前
23种设计模式
android·java·设计模式