1.简介
桥接(Bridge)设计模式是一种结构型设计模式,它的目的是将抽象与其实现分离,使得两者可以独立地变化。这种模式特别适用于系统需要支持多种不同的平台或环境,并且这些平台或环境可能经常发生变化的情况。
在C语言中实现桥接模式需要考虑如何使用指针和函数指针来模拟对象的多态性。下面我们将通过一个简单的例子来解释桥接模式的概念及其在C语言中的应用。
2.通俗讲解
假设我们正在开发一个图形库,这个图形库需要支持不同类型的渲染器(例如OpenGL和DirectX)。我们的目标是让图形形状(如圆形、矩形等)能够独立于渲染器而存在,并且可以在运行时动态选择渲染器。
3.实战
设计
Shape:定义形状的抽象类。
Renderer:定义渲染器的抽象类。
Concrete Shape:具体的形状类,如Circle、Rectangle。
Concrete Renderer:具体的渲染器类,如OpenGLRenderer、DirectXRenderer。
3.1.代码
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// Renderer interface
typedef struct Renderer {
void (*draw_line)(int x1, int y1, int x2, int y2);
} Renderer;
typedef void (*RendererDrawLineFunc)(int x1, int y1, int x2, int y2);
// Concrete Renderer - OpenGL
typedef struct OpenGLRenderer {
Renderer base;
} OpenGLRenderer;
void opengl_draw_line(int x1, int y1, int x2, int y2) {
printf("OpenGL: Drawing line from (%d, %d) to (%d, %d)\n", x1, y1, x2, y2);
}
OpenGLRenderer *create_opengl_renderer() {
OpenGLRenderer *renderer = malloc(sizeof(OpenGLRenderer));
renderer->base.draw_line = opengl_draw_line;
return renderer;
}
// Concrete Renderer - DirectX
typedef struct DirectXRenderer {
Renderer base;
} DirectXRenderer;
void directx_draw_line(int x1, int y1, int x2, int y2) {
printf("DirectX: Drawing line from (%d, %d) to (%d, %d)\n", x1, y1, x2, y2);
}
DirectXRenderer *create_directx_renderer() {
DirectXRenderer *renderer = malloc(sizeof(DirectXRenderer));
renderer->base.draw_line = directx_draw_line;
return renderer;
}
// Shape interface
typedef struct Shape {
Renderer *renderer;
void (*draw)();
} Shape;
// Concrete Shape - Circle
typedef struct Circle {
Shape base;
int x, y, radius;
} Circle;
void circle_draw(Circle *circle) {
int i;
for (i = 0; i <= 360; i++) {
int x = circle->x + circle->radius * cos(i * M_PI / 180.0);
int y = circle->y + circle->radius * sin(i * M_PI / 180.0);
circle->base.renderer->draw_line(circle->x, circle->y, x, y);
}
}
Circle *create_circle(Renderer *renderer, int x, int y, int radius) {
Circle *circle = malloc(sizeof(Circle));
circle->base.renderer = renderer;
circle->base.draw = circle_draw;
circle->x = x;
circle->y = y;
circle->radius = radius;
return circle;
}
// Concrete Shape - Rectangle
typedef struct Rectangle {
Shape base;
int x, y, width, height;
} Rectangle;
void rectangle_draw(Rectangle *rectangle) {
int x1 = rectangle->x, y1 = rectangle->y;
int x2 = rectangle->x + rectangle->width, y2 = rectangle->y;
int x3 = rectangle->x + rectangle->width, y3 = rectangle->y + rectangle->height;
int x4 = rectangle->x, y4 = rectangle->y + rectangle->height;
rectangle->base.renderer->draw_line(x1, y1, x2, y2);
rectangle->base.renderer->draw_line(x2, y2, x3, y3);
rectangle->base.renderer->draw_line(x3, y3, x4, y4);
rectangle->base.renderer->draw_line(x4, y4, x1, y1);
}
Rectangle *create_rectangle(Renderer *renderer, int x, int y, int width, int height) {
Rectangle *rectangle = malloc(sizeof(Rectangle));
rectangle->base.renderer = renderer;
rectangle->base.draw = rectangle_draw;
rectangle->x = x;
rectangle->y = y;
rectangle->width = width;
rectangle->height = height;
return rectangle;
}
int main() {
OpenGLRenderer *opengl_renderer = create_opengl_renderer();
DirectXRenderer *directx_renderer = create_directx_renderer();
Circle *circle = create_circle(opengl_renderer, 100, 100, 50);
circle->base.draw(circle); // Draw the circle using OpenGL
Rectangle *rectangle = create_rectangle(directx_renderer, 200, 200, 100, 50);
rectangle->base.draw(rectangle); // Draw the rectangle using DirectX
free(circle);
free(rectangle);
free(opengl_renderer);
free(directx_renderer);
return 0;
}
3.2.代码解析
我们会创建一个简单的图形库,该库支持两种渲染器:OpenGL 和 DirectX。我们将定义一个形状接口 Shape 和两个具体的形状类 Circle 和 Rectangle,同时也会定义一个渲染器接口 Renderer 和两个具体的渲染器类 OpenGLRenderer 和 DirectXRenderer。
在上面的例子中,我们通过使用指针和函数指针来实现了桥接模式。Shape
类并不关心具体的渲染器是什么,它只关心渲染器能做什么(即draw_line
方法)。这样,我们可以轻松地添加新的渲染器或新的形状,而无需修改现有的代码。
3.3.代码运行
OpenGL: Drawing line from (100, 100) to (149, 100)
OpenGL: Drawing line from (149, 100) to (146, 104)
OpenGL: Drawing line from (146, 104) to (141, 108)
...
OpenGL: Drawing line from (100, 149) to (100, 100)
DirectX: Drawing line from (200, 200) to (300, 200)
DirectX: Drawing line from (300, 200) to (300, 250)
DirectX: Drawing line from (300, 250) to (200, 250)
DirectX: Drawing line from (200, 250) to (200, 200)
3.4.结果分析
从输出结果中可以看出,圆形使用了 OpenGL 渲染器进行绘制,而矩形则使用了 DirectX 渲染器进行绘制。每次调用 draw
方法时,都会根据传入的渲染器类型来决定如何绘制形状。
桥接模式通过将形状和渲染器解耦,允许我们在不改变现有代码的情况下添加新的形状或者新的渲染器。例如,如果将来要添加一个新的渲染器,比如 Vulkan 或 Metal,只需要简单地添加一个新的渲染器类即可,而不需要修改现有的形状类。同样,如果需要添加新的形状,也只需添加一个新的形状类,而不必修改现有的渲染器类。
这种设计方式提高了代码的灵活性和可维护性,使得系统更加易于扩展和管理。
4.总结
桥接模式是一种结构型设计模式,它允许将抽象部分与它的实现部分分离,从而使它们可以独立地变化。在这个例子中,我们创建了一个图形库,支持不同的渲染器(如OpenGL和DirectX),并且能够绘制不同的形状(如圆形和矩形)。
** 关键点 **
- 分离关注点: 将形状的定义与渲染机制分离,使我们可以独立地扩展形状种类和渲染器类型。
- 接口定义: 使用接口(或抽象类)定义抽象部分和实现部分,确保它们之间的一致性和互操作性。
- 组合而非继承: 形状类通过组合一个渲染器对象来实现绘制行为,而不是通过继承来实现。
优点
- 灵活性: 可以很容易地添加新的形状或新的渲染器。
- 可扩展性: 新的形状或渲染器的添加不会影响到其他部分的代码。
- 代码重用: 形状类和渲染器类都可以被多个不同的类复用。
缺点
- 复杂度: 相比于更简单的解决方案,桥接模式引入了更多的类和接口,这可能会增加系统的复杂度。
- 初始化: 在创建形状时需要额外传递渲染器对象,这可能会导致更多的初始化工作。
应用场景
桥接模式适用于需要在运行时动态选择不同实现的情况,特别是当这些实现的变化可能会频繁发生时。
这个简单的图形库示例展示了桥接模式的基本应用。在实际项目中,桥接模式可以用于更复杂的场景,例如跨平台应用程序开发、游戏引擎中的图形渲染等。