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

news/2024/11/7 21:00:25/

c++20设计模式

  • 第 7 章 桥接模式
    • 7.1 Pimpl模式
    • 7.2 桥接模式介绍
    • 7.3 总结
    • 7.4 代码

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

http://www.ppmy.cn/news/1272252.html

相关文章

深入解析 Spring 和 Spring Boot 的区别

目录 引言 1. 设计理念 1.1 Spring 框架的设计理念 1.2 Spring Boot 的设计理念 2. 项目配置 2.1 Spring 框架的项目配置 2.2 Spring Boot 的项目配置 3. 自动配置 3.1 Spring 框架的自动配置 3.2 Spring Boot 的自动配置 4. 微服务支持 4.1 Spring 框架的微服务支持…

SpringIOC之LoadTimeWeavingConfigurer

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

YOLO v8 目标检测识别翻栏

一、行人翻栏识别背景介绍 1.1跨越围栏是人类活动中一个普遍但需要引起警惕的行为。它不仅可能导致各种意外事故&#xff0c;甚至可能对个人的生命安全构成威胁。在交通领域&#xff0c;跨越围栏可能导致严重的交通事故&#xff0c;造成人员伤亡。在公共场所&#xff0c;如公园…

Redis - 分布式锁、Redisson

分布式锁 分布式锁是控制分布式系统间同步访问共享资源的一种方式&#xff0c;其可以保证共享资源在并 发场景下的数据一致性。 当有多个线程要访问某一个共享资源&#xff08; DBMS 中的数据或 Redis 中的数据&#xff0c;或共享文件 等&#xff09;时&#xff0c;为了达…

2023自动化测试框架大对比:哪个更胜一筹?

所谓工欲善其事&#xff0c;必先利其器&#xff0c;在进行自动化测试时&#xff0c;选择一个合适的框架是至关重要的。因为一个好的测试框架可以大大提高测试效率&#xff0c;减少我们很多工作量。在选择框架之前&#xff0c;我们通常需要对不同的框架进行对比&#xff0c;以便…

BabylonJS(一) 前言-为什么想写这个系列

先开篇吐槽下吧&#xff0c;我是奔着6.0和WebGPU来的&#xff0c;网上各种评测也很优秀&#xff0c;社区活跃&#xff0c;打算入坑。 但...... babylonjs中文资料相对于Threejs、Unity简直是太少了.. 之前有个中文站点&#xff0c;好像也没啥人维护了&#xff0c;大部分deep…

基本运算byte b3 = b1 + b2和 b1+=b2

基本赋值运算符&#xff1a; 扩展赋值运算符&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/,%&#xff0c;>>&#xff0c;<<&#xff0c;&… 注意&#xff1a;无论是基本赋值运算符还是扩展赋值运算符都是最后算&#xff0c;并且是把右边的计算的结果最…

k8s中EmptyDir、HostPath、NFS三种基本存储方式介绍

目录 一.数据存储介绍 二.EmptyDir 1.简介 2.案例演示 三.HostPath 1.简介 2.案例演示 &#xff08;1&#xff09;介绍一下type类型 &#xff08;2&#xff09;简单演示 &#xff08;3&#xff09;数据同步功能 四.NFS 1.简介 2.案例演示 &#xff08;1&#xff…