本节主要介绍结构型模式中的桥接模式 。为方便理解,从本节开始的设计模式将使用C#编写。
1.概述
桥接模式(Bridge Pattern) 是一种结构型设计模式,用于将抽象部分与实现部分分离,使它们可以独立变化。它使用组合关系代替继承关系,从而降低了抽象和实现这两个可变维度的耦合度。
2.结构
(1) 实现化(Implementor):定义实现类的接口,供抽象化角色调用
(2) 具体实现化(Concrete Implementor):实现实现化接口
(3) 抽象化(Abstraction):定义抽象接口,维护一个对实现化对象的引用
(4) 扩展抽象化(Refined Abstraction):扩展抽象化,改变和修正父类对抽象化的定义
3.代码示例
(1) 使用不同的引擎渲染图形
假如我们需要使用两种不同的引擎来渲染圆形、正方形和三角形,于是我们定义了一个通用的渲染引擎接口:
C#
// 实现化 - 定义渲染引擎接口
public interface IRenderEngine
{
// 渲染圆形
void RenderCircle(float radius);
// 渲染正方形
void RenderSquare(float sideLength);
// 渲染三角形
void RenderTriangle(float sideLength, float sideHeight);
}
有了渲染引擎的接口,我们便可以具体实现化该引擎接口:
- OpenGL
C#
// OpenGL渲染引擎实现
public class OpenGLRenderer : IRenderEngine
{
// 渲染圆形
public void RenderCircle(float radius)
{
Console.WriteLine($"OpenGL:绘制圆形,半径:{radius}");
}
// 渲染正方形
public void RenderSquare(float sideLength)
{
Console.WriteLine($"OpenGL:绘制正方形,边长:{sideLength}");
}
// 渲染三角形
public void RenderTriangle(float sideLength, float sideHeight)
{
Console.WriteLine($"OpenGL:绘制三角形,底:{sideLength},高:{sideHeight}");
}
}
- DirectX
C#
// DirectX渲染引擎实现
public class DirectXRenderer : IRenderEngine
{
// 渲染圆形
public void RenderCircle(float radius)
{
Console.WriteLine($"DirectX:绘制圆形,半径:{radius}");
}
// 渲染正方形
public void RenderSquare(float sideLength)
{
Console.WriteLine($"DirectX:绘制正方形,边长:{sideLength}");
}
// 渲染三角形
public void RenderTriangle(float sideLength, float sideHeight)
{
Console.WriteLine($"DirectX:绘制三角形,底:{sideLength},高:{sideHeight}");
}
}
接下来,我们就能够通过渲染引擎接口去绘制图形了。
由于图形有三种,我们需要先定义一个形状抽象类,后续实现类便可以通过继承该类对形状进行各自的具体实现。
C#
// 定义形状抽象类
public abstract class Shape
{
// 渲染引擎的载体
protected IRenderEngine? renderEngine;
// 构造函数,接受一个渲染引擎实例
protected Shape(IRenderEngine? renderEngine)
{
this.renderEngine = renderEngine;
}
// 抽象方法,绘制形状
public abstract void Draw();
// 抽象方法,调整形状大小
public abstract void Resize(float factor);
}
====================
// 定义圆形类,继承自形状抽象类
public class Circle : Shape
{
private float radius;
public Circle(float radius,IRenderEngine? renderEngine) : base(renderEngine)
{
this.radius = radius;
}
// 绘制圆形
public override void Draw()
{
renderEngine?.RenderCircle(radius);
}
// 调整圆形大小
public override void Resize(float factor)
{
// 调整半径
radius *= factor;// 将当前半径乘以系数
// 输出调整后的半径
Console.WriteLine($"圆形大小调整为:半径{radius}");
}
}
====================
// 定义正方形类,继承自形状抽象类
public class Square : Shape
{
private float sideLength;
public Square(float sideLength, IRenderEngine? renderEngine) : base(renderEngine)
{
this.sideLength = sideLength;
}
// 绘制正方形
public override void Draw()
{
renderEngine?.RenderSquare(sideLength);
}
// 调整正方形大小
public override void Resize(float factor)
{
// 调整边长
sideLength *= factor;// 将当前边长乘以系数
// 输出调整后的边长
Console.WriteLine($"正方形大小调整为:边长{sideLength}");
}
}
====================
// 定义三角形类,继承自形状抽象类
public class Triangle : Shape
{
private float sideLength;
private float sideHeight;
public Triangle(float sideLength, float sideHeight, IRenderEngine? renderEngine) : base(renderEngine)
{
this.sideLength = sideLength;
this.sideHeight = sideHeight;
}
// 绘制三角形
public override void Draw()
{
renderEngine?.RenderTriangle(sideLength, sideHeight);
}
// 调整三角形大小
public override void Resize(float factor)
{
// 调整底和高
sideLength *= factor;// 将当前底乘以系数
sideHeight *= factor;// 将当前高乘以系数
// 输出调整后的底和高
Console.WriteLine($"三角形大小调整为:底{sideLength},高{sideHeight}");
}
}
最后便是在客户端对图形进行绘制:
C#
Console.WriteLine("Hello, World!");
// 创建不同的渲染引擎实例
IRenderEngine openGL = new OpenGLRenderer();
IRenderEngine directX = new DirectXRenderer();
Console.WriteLine("== 使用OpenGL渲染 ==");
// 创建不同形状,使用OpenGL渲染
Shape circle = new Circle(5, openGL);
Shape square = new Square(10, openGL);
Shape triangle = new Triangle(6, 8, openGL);
circle.Draw();
square.Draw();
triangle.Draw();
Console.WriteLine("\n=== 使用DirectX渲染 ===");
// 创建不同形状,使用DirectX渲染
circle = new Circle(5, directX);
square = new Square(10, directX);
triangle = new Triangle(6, 8, directX);
circle.Draw();
square.Draw();
triangle.Draw();
Console.WriteLine("\n=== 调整大小测试 ===");
// 调整大小测试
circle.Resize(2);
circle.Draw();
square.Resize(0.5f);
square.Draw();
// 动态切换渲染引擎(实际应用中可能更复杂)
Console.WriteLine("\n=== 动态切换渲染引擎 ===");
Shape shape = new Circle(7, openGL);
shape.Draw();
// 在实际应用中,可能需要更复杂的方式来动态切换渲染引擎
// 这里仅为演示
Console.WriteLine("切换到DirectX渲染...");
// 注:在实际设计中,可能需要重构Shape类以支持动态切换渲染引擎
于是我们在控制台中得到如下效果:
bash
Hello, World!
== 使用OpenGL渲染 ==
OpenGL:绘制圆形,半径:5
OpenGL:绘制正方形,边长:10
OpenGL:绘制三角形,底:6,高:8
=== 使用DirectX渲染 ===
DirectX:绘制圆形,半径:5
DirectX:绘制正方形,边长:10
DirectX:绘制三角形,底:6,高:8
=== 调整大小测试 ===
圆形大小调整为:半径10
DirectX:绘制圆形,半径:10
正方形大小调整为:边长5
DirectX:绘制正方形,边长:5
=== 动态切换渲染引擎 ===
OpenGL:绘制圆形,半径:7
切换到DirectX渲染...
如果你看过上例仍不能理解,下面可以再举一例。
(2) 消息发送系统
现在我们有一个消息发送系统,它能通过邮件、短信和推送向用户发送三类信息------普通消息、紧急信息和加密信息。
所以我们需要一个承载消息发送方式的接口:
C#
// 消息发送接口
public interface IMessageSender
{
void SendMessage(string title, string body, string recipient);
}
通过接口可以实现三种具体的发送方式:
C#
// 邮件发送实现类
public class EmailSender : IMessageSender
{
public void SendMessage(string title, string body, string recipient)
{
Console.WriteLine($"发送邮件到:{recipient}");
Console.WriteLine($"标题:{title}");
Console.WriteLine($"内容:{body}");
Console.WriteLine($"--- 邮件已经发送 ---\n");
}
}
====================
// 短信发送实现类
public void SendMessage(string title, string body, string recipient)
{
Console.WriteLine($"发送短信到: {recipient}");
Console.WriteLine($"{title}: {body}");
Console.WriteLine("--- 短信已发送 ---\n");
}
====================
// 推送通知发送实现类
public class PushNotificationSender : IMessageSender
{
public void SendMessage(string title, string body, string recipient)
{
Console.WriteLine($"发送推送通知到: {recipient}");
Console.WriteLine($"{title}: {body}");
Console.WriteLine("--- 推送通知已发送 ---\n");
}
}
再使用抽象出一个消息类用于对消息内容进行对消息的初步定义,其中包含消息的基本属性和发送方法。
C#
// 抽象消息类:对消息的初步定义,包含消息的基本属性和发送方法
public abstract class Message
{
protected IMessageSender? messageSender;
public string? Title { get; set; }
public string? Body { get; set; }
public string? Recipient { get; set; }
protected Message(IMessageSender? messageSender)
{
this.messageSender = messageSender;
}
public abstract void Send();
// 可以添加其它的方法
// 例如验证消息内容是否合法
public void Validate()
{
if (string.IsNullOrEmpty(Recipient))
throw new ArgumentException("收件人不能为空");
if (string.IsNullOrEmpty(Title))
throw new ArgumentException("标题不能为空");
}
}
通过对抽象类的具体实现便得到了三种具体的消息类型:
C#
// 普通信息类
public class NormalMessage : Message
{
public NormalMessage(IMessageSender? messageSender) : base(messageSender)
{
}
public override void Send()
{
Validate();
Console.WriteLine("发送普通信息......");
messageSender?.SendMessage(Title!, Body!, Recipient!);// 使用非空断言运算符,因为在 Validate 方法中已经验证过这些属性不为空
}
}
====================
// 紧急信息类
public class UrgentMessage : Message
{
public UrgentMessage(IMessageSender? messageSender) : base(messageSender)
{
}
public override void Send()
{
Validate();
Console.WriteLine("发送紧急信息......");
messageSender?.SendMessage($"[紧急]{Title!}", Body!, Recipient!);// 使用非空断言运算符,因为在 Validate 方法中已经验证过这些属性不为空
}
}
====================
public class EncryptedMessage : Message
{
public EncryptedMessage(IMessageSender? messageSender) : base(messageSender)
{
}
public override void Send()
{
Validate();
Console.WriteLine("发送加密信息......");
// 简单的加密示例:将正文进行Base64编码
string encryptedBody = Encrypt(Body!);
messageSender?.SendMessage($"[加密]{Title!}", encryptedBody, Recipient!);// 使用非空断言运算符,因为在 Validate 方法中已经验证过这些属性不为空
}
// 简单的加密方法(Base64编码)
private string Encrypt(string text)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
}
}
最后让我们在客户端模拟消息发送的过程。
C#
Console.WriteLine("Hello, World!");
// 创建不同的消息发送方式
IMessageSender emailSender = new EmailSender();
IMessageSender smsSender = new SmsSender();
IMessageSender pushSender = new PushNotificationSender();
Console.WriteLine("=== 邮件发送示例 ===");
// 发送普通邮件
Message emailMessage = new NormalMessage(emailSender)
{
Title = "会议通知",
Body = "明天下午3点召开项目会议",
Recipient = "zhangsan@company.com"
};
emailMessage.Send();
// 发送紧急邮件
Message urgentEmail = new UrgentMessage(emailSender)
{
Title = "系统维护",
Body = "今晚12点系统将进行维护",
Recipient = "all@company.com"
};
urgentEmail.Send();
Console.WriteLine("=== 短信发送示例 ===");
// 发送短信
Message smsMessage = new NormalMessage(smsSender)
{
Title = "验证码",
Body = "您的验证码是: 123456",
Recipient = "13800138000"
};
smsMessage.Send();
Console.WriteLine("=== 推送通知示例 ===");
// 发送推送通知
Message pushMessage = new EncryptedMessage(pushSender)
{
Title = "新消息",
Body = "您有一条新的私密消息",
Recipient = "device_token_abc123"
};
pushMessage.Send();
// 演示动态切换发送方式
Console.WriteLine("=== 动态切换发送方式 ===");
Message message = new UrgentMessage(emailSender)
{
Title = "测试消息",
Body = "这是一条测试消息",
Recipient = "test@example.com"
};
message.Send();
// 在实际应用中,可以根据条件动态选择发送方式
Console.WriteLine("根据用户偏好切换发送方式...");
// 这里可以根据业务逻辑选择不同的发送方式
控制台输出结果:
bash
Hello, World!
=== 邮件发送示例 ===
发送普通信息......
发送邮件到:zhangsan@company.com
标题:会议通知
内容:明天下午3点召开项目会议
--- 邮件已经发送 ---
发送紧急信息......
发送邮件到:all@company.com
标题:[紧急]系统维护
内容:今晚12点系统将进行维护
--- 邮件已经发送 ---
=== 短信发送示例 ===
发送普通信息......
发送短信到: 13800138000
验证码: 您的验证码是: 123456
--- 短信已发送 ---
=== 推送通知示例 ===
发送加密信息......
发送推送通知到: device_token_abc123
[加密]新消息: 5oKo5pyJ5LiA5p2h5paw55qE56eB5a+G5raI5oGv
--- 推送通知已发送 ---
=== 动态切换发送方式 ===
发送紧急信息......
发送邮件到:test@example.com
标题:[紧急]测试消息
内容:这是一条测试消息
--- 邮件已经发送 ---
根据用户偏好切换发送方式...
4.桥接模式的优势
优点:
分离抽象和实现:抽象和实现可以独立扩展,不会相互影响
提高系统可扩展性:可以独立地对抽象和实现进行扩展
实现细节对客户透明:客户可以专注于抽象接口,而不必关心具体实现
符合开闭原则:新增抽象或实现时,不需要修改现有代码
缺点:
增加系统复杂度:需要正确地识别出系统中两个独立变化的维度
需要正确设计抽象接口:设计难度相对较大
5. 适用场景
当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时
如:形状(圆形、方形)和颜色(红色、蓝色)
当一个系统不希望使用继承,或因为多层继承导致类的个数急剧增加时
当一个系统需要在构件的抽象化和具体化之间增加更多的灵活性时
常见应用场景:
GUI框架中的窗口和平台实现
跨平台应用程序开发
数据库驱动程序
日志记录器(日志级别和输出目标)
6. 与其他模式的关系
与适配器模式:适配器模式主要改变已有对象的接口,而桥接模式是将抽象和实现分离
与抽象工厂模式:抽象工厂模式可以和桥接模式一起使用,为特定的实现创建合适的抽象
与策略模式:两者结构相似,但意图不同。策略模式关注算法族的选择,桥接模式关注抽象和实现的分离
7. 实际应用建议
识别变化维度:在设计时,仔细分析系统中哪些维度会独立变化
优先使用组合:当发现需要多层继承时,考虑使用桥接模式
避免过度设计:如果变化维度不多或相对稳定,可能不需要使用桥接模式
桥接模式通过将抽象和实现分离,提供了一种比继承更灵活的设计方案,特别适合处理多维度变化的系统设计。