《C++20设计模式》学习笔记——第7章桥接模式
桥接模式
桥接模式的概念很简单,它通常作为连接器或黏合剂,将两个“不相关”的组件连接起来。抽象(接口)的使用允许组件之间在不了解具体实现的情况下彼此关互。
1. Pimpl模式
以Person类的定义为例,将具体的实现隐藏在另一个类中即PersonImpl。并且PersonImpl类不是在头文件中定义的,而是驻留在.cpp文件(Person.cpp,因此Person和PersonImpl放在一起)。
struct Person
{
std::string name_;
void greet();
Person();
~Person();
class PersonImpl;
PersonImpl* impl_;
};
class Person::PersonImpl
{
public:
void greet(Person* p);
};
void Person::PersonImpl::greet(Person* p)
{
printf("hello %s", p->name_.c_str());
}
Person::Person() : impl_{new PersonImpl} {}
Person::~Person() {delete impl_;}
void Person::greet()
{
impl_->greet(this);
}
类Person中对PersonImpl做了前向声明,并且保存了一个PersonImpl类型的指针。我们在Person的构造函数中初始化这个指针,并且在析构函数中销毁它。(也可以替换为智能指针)。这种方法有三个优点:
- 隐藏了类的大部分实现。如果
Person类有许多私有/公共成员,即使由于private/protected访问限定符的存在,客户不能直接访问这些成员,但我们也会提供一系列丰富的API,由此暴露了Person类内部的某些成员。如果使用Pimpl模式,那么只需要对外提供公共接口即可。 - 修改隐藏的Impl类的数据成员不会影响二进制文件的兼容性。
- 头文件中只需要包含声明所需的头文件,而不必包含实现所需的头文件。例如,如果
Person类有一个vector<string>类型的私有成员,则必须在Person.h头文件中同时包含<vector>和<string>(这是可传递的,所以任何使用Person.h的文件都包含它们)。使用Pimpl模式,这可以在.cpp文件中完成。
Pimpl模式可以使系统中的头文件更加整洁,而且不必频繁改动。不过,副作用是会影响编译速度。Pimpl模式是桥接模式的一种很好的体现:它是一种不透明指针,起着桥梁的作用,将公共接口的成员与其隐藏在.cpp文件中的底层实现结合了起来。
2. 桥接模式介绍
桥接模式的实现用一句话来概括就是:一个基类中含有对另一个类型的引用。
以两种(数学意义上的)对象为例:几何对象,以及将几何对象绘制在屏幕上的渲染器对象。
假设我们可以使用向量和光栅形式进行渲染(这里不会编写任何实际的绘图代码,而是编写一些打印到控制台的代码来模拟实际的渲染过程),并且将几何对象的形状限制为圆形。
//基类Renderer
struct Renderer
{
virtual void render_circle(float x, float y, float radius) = 0;
};
//向量渲染器
struct VectorRenderer : Renderer
{
void render_circle(float x, float y, float radius) override
{
std::cout << "Drawing a vector circle of radius " << radius << std::endl;
}
};
//光栅渲染器
struct RasterRenderer : Renderer
{
void render_circle(float x, float y, float radius) override
{
std::cout << "Rasterizing circle of radius " << radius << std::endl;
}
};
//几何对象的基类Shape:保存一个对渲染器的引用
struct Shape
{
protected:
Renderer& renderer_;
Shape(Renderer& ren) : renderer_{ren} {}
public:
virtual void draw() = 0;
virtual void resize(float factor) = 0;
};
//圆形的具体实现
struct Circle : Shape
{
float x_, y_, radius_;
Circle(Renderer& ren, float x, float y, float rad)
: Shape{ren}, x_{x}, y_{y}, radius_{rad} {}
void draw() override
{
renderer_.render_circle(x_, y_, radius_);
}
void resize(float fac) override
{
radius_ *= fac;
}
};
上述代码中,Shape类含有一个Renderer类型的引用,这正是桥接模式中的“桥”之所在。最有趣的部分在于函数draw():这是连接Circle和渲染过程的桥梁。这里的桥就是渲染器。例如:
RasterRenderer rr;
Circle r_circle(rr, 5,5,5);
r_circle.draw();
r_circle.resize(2);
r_circle.draw();
在这段代码中,桥就是RasterRenderer:我们声明一个RasterRenderer对象,并将其引用传递给Circle。此后,对函数draw()的调用将会以此RasterRenderer引用为桥梁来对Circle进行渲染。如果需要调整圆的大小,则可以调用resize(),渲染过程仍旧可以正常进行,因为渲染器并不知道也不关心其所渲染的Circle对象。
3.总结
桥接模式的概念很简单,它通常作为连接器或黏合剂,将两个“不相关”的组件连接起来。抽象(接口)的使用允许组件之间在不了解具体实现的情况下彼此关互。
也就是说,桥接模式的参与者确实需要意识到彼此的存在。具体来说,Circle类需要引用Renderer;Renderer也需要知道如何绘制圆。这与中介者模式形成了对比,中介者模式允许对象在毫不知晓对方的情况下进行通信。
更多推荐



所有评论(0)