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

第 7 章 桥接模式

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

7.1 Pimpl模式

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

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放在一起)。它的定义非常简单:

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

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

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

}

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

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

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定义如下:

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";
    }
};

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

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

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

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(包含位置和大小信息)和渲染过程的桥梁。这里的桥就是渲染器,例如:

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 代码

#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;
}

相关推荐

  1. C++20设计模式》---模式学习笔记

    2023-12-17 07:18:03       76 阅读
  2. 设计模式模式-学习记录

    2023-12-17 07:18:03       28 阅读
  3. C++20设计模式学习笔记---原型模式

    2023-12-17 07:18:03       53 阅读
  4. C++20设计模式学习笔记---单例模式

    2023-12-17 07:18:03       40 阅读
  5. C++20设计模式》---原型模式学习笔记代码

    2023-12-17 07:18:03       51 阅读
  6. 设计模式

    2023-12-17 07:18:03       42 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-17 07:18:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-17 07:18:03       100 阅读
  3. 在Django里面运行非项目文件

    2023-12-17 07:18:03       82 阅读
  4. Python语言-面向对象

    2023-12-17 07:18:03       91 阅读

热门阅读

  1. Ansible如何处理play错误的?Ansible角色?

    2023-12-17 07:18:03       65 阅读
  2. Electron学习第一天 ,启动项目

    2023-12-17 07:18:03       66 阅读
  3. 【Mypy】超级实用的python高级库!

    2023-12-17 07:18:03       59 阅读
  4. 爬虫框架beautifulsoup详解

    2023-12-17 07:18:03       49 阅读
  5. 57. Insert Interval

    2023-12-17 07:18:03       68 阅读
  6. 用sqlite制作对局记录管理

    2023-12-17 07:18:03       52 阅读
  7. 【OpenCV】实时屏幕捕获

    2023-12-17 07:18:03       78 阅读
  8. 使用Python进行数据可视化

    2023-12-17 07:18:03       67 阅读
  9. spring 笔记十 Spring事务管理

    2023-12-17 07:18:03       59 阅读
  10. 冒泡排序学习

    2023-12-17 07:18:03       56 阅读