桥接模式
大约 12 分钟约 3483 字
桥接模式
简介
桥接(Bridge)将抽象部分与实现部分分离,使它们都可以独立变化。理解桥接模式,有助于在多维变化场景中避免类爆炸问题。
桥接模式的核心问题是"类爆炸"。当你有两个独立变化的维度(例如"消息类型"和"发送渠道"),如果用继承来实现所有组合,类的数量会是两个维度数量的乘积。桥接模式通过将这两个维度拆分为独立的继承层次,并通过组合(而非继承)关联它们,使类数量从 m x n 降低到 m + n。
GoF 将桥接模式描述为:"将抽象与实现分离,使它们可以独立变化。" 这里的"实现"不是指具体的代码实现,而是一个独立的抽象层次。
特点
结构分析
UML 类图
+--------------------+ +--------------------+
| Abstraction | | Implementor |
+--------------------+ +--------------------+
| -impl: Implementor |---------->| +Operation() |
+--------------------+ +--------------------+
| +Operation() | ^
+--------------------+ +---------+---------+
^ | |
+-------+-------+ +------+------+ +--------+--------+
| RefinedAbstr | | ConcreteImplA | | ConcreteImplB |
+-------------+ +--------------+ +----------------+类爆炸问题
不使用桥接 — 类爆炸 (m x n):
EmailAlert + SmsAlert + WechatAlert + ...
EmailReport + SmsReport + WechatReport + ...
EmailMarketing + SmsMarketing + WechatMarketing + ...
= 3 x 3 = 9 个类
使用桥接 — 线性增长 (m + n):
Alert / Report / Marketing (3 个抽象)
Email / SMS / WeChat (3 个实现)
= 3 + 3 = 6 个类实现
消息通知桥接
// 实现接口 — 发送渠道
public interface IMessageSender
{
Task SendAsync(string recipient, string subject, string body);
}
public class EmailSender : IMessageSender
{
public async Task SendAsync(string recipient, string subject, string body)
{
Console.WriteLine($"[Email] 发送给 {recipient}: {subject}");
await Task.CompletedTask;
}
}
public class SmsSender : IMessageSender
{
public async Task SendAsync(string recipient, string subject, string body)
{
Console.WriteLine($"[SMS] 发送给 {recipient}: {body}");
await Task.CompletedTask;
}
}
public class WeChatSender : IMessageSender
{
public async Task SendAsync(string recipient, string subject, string body)
{
Console.WriteLine($"[微信] 推送给 {recipient}: {subject}");
await Task.CompletedTask;
}
}
// 抽象部分 — 消息类型
public abstract class Notification
{
protected IMessageSender Sender { get; }
protected Notification(IMessageSender sender) => Sender = sender;
public abstract Task NotifyAsync(string recipient, string message);
}
// 扩展抽象 — 不同类型的消息
public class AlertNotification : Notification
{
public AlertNotification(IMessageSender sender) : base(sender) { }
public override async Task NotifyAsync(string recipient, string message)
{
var subject = "系统告警";
var body = $"紧急通知: {message}\n时间: {DateTime.UtcNow:yyyy-MM-dd HH:mm}";
await Sender.SendAsync(recipient, subject, body);
}
}
public class ReportNotification : Notification
{
public ReportNotification(IMessageSender sender) : base(sender) { }
public override async Task NotifyAsync(string recipient, string message)
{
var subject = "定期报告";
var body = $"报告摘要: {message}";
await Sender.SendAsync(recipient, subject, body);
}
}
public class MarketingNotification : Notification
{
public MarketingNotification(IMessageSender sender) : base(sender) { }
public override async Task NotifyAsync(string recipient, string message)
{
var subject = "活动通知";
var body = $"亲爱的用户: {message}";
await Sender.SendAsync(recipient, subject, body);
}
}
// 使用 — 消息类型与发送渠道自由组合
var alertByEmail = new AlertNotification(new EmailSender());
var alertBySms = new AlertNotification(new SmsSender());
var marketingByWeChat = new MarketingNotification(new WeChatSender());
await alertByEmail.NotifyAsync("admin@example.com", "CPU 使用率超过 90%");
await alertBySms.NotifyAsync("138xxxx1234", "CPU 使用率超过 90%");
await marketingByWeChat.NotifyAsync("user_001", "春季大促开始啦!");数据渲染桥接
// 实现接口 — 渲染器
public interface IRenderer
{
string RenderHeader(string title);
string RenderRow(params string[] columns);
string RenderFooter();
}
public class HtmlRenderer : IRenderer
{
public string RenderHeader(string title) => $"<table><caption>{title}</caption>";
public string RenderRow(params string[] cols) => $"<tr>{string.Join("", cols.Select(c => $"<td>{c}</td>"))}</tr>";
public string RenderFooter() => "</table>";
}
public class MarkdownRenderer : IRenderer
{
public string RenderHeader(string title) => $"## {title}\n";
public string RenderRow(params string[] cols) => $"| {string.Join(" | ", cols)} |";
public string RenderFooter() => "";
}
public class CsvRenderer : IRenderer
{
public string RenderHeader(string title) => $"# {title}\n";
public string RenderRow(params string[] cols) => string.Join(",", cols);
public string RenderFooter() => "";
}
// 抽象 — 报表
public abstract class Report
{
protected IRenderer Renderer { get; }
protected Report(IRenderer renderer) => Renderer = renderer;
public abstract string Generate();
}
public class SalesReport : Report
{
private readonly List<(string Product, int Qty, decimal Amount)> _data = new();
public SalesReport(IRenderer renderer) : base(renderer) { }
public void AddRow(string product, int qty, decimal amount) => _data.Add((product, qty, amount));
public override string Generate()
{
var sb = new StringBuilder();
sb.AppendLine(Renderer.RenderHeader("销售报表"));
foreach (var row in _data)
sb.AppendLine(Renderer.RenderRow(row.Product, row.Qty.ToString(), $"Y{row.Amount:N2}"));
sb.Append(Renderer.RenderFooter());
return sb.ToString();
}
}
// 使用
var htmlReport = new SalesReport(new HtmlRenderer());
htmlReport.AddRow("笔记本", 10, 50000);
htmlReport.AddRow("手机", 25, 125000);
Console.WriteLine(htmlReport.Generate());
var csvReport = new SalesReport(new CsvRenderer());
csvReport.AddRow("笔记本", 10, 50000);
Console.WriteLine(csvReport.Generate());实战:日志框架桥接
日志框架是桥接模式的经典应用场景。日志级别(Debug、Info、Error)和日志输出目标(控制台、文件、数据库)是两个独立变化的维度。
// 实现接口 — 日志输出目标
public interface ILogAppender
{
void Append(LogLevel level, string message, Exception? ex);
}
public class ConsoleAppender : ILogAppender
{
public void Append(LogLevel level, string message, Exception? ex)
{
Console.WriteLine($"[{DateTime.UtcNow:HH:mm:ss}] [{level}] {message}");
if (ex != null) Console.WriteLine($" Exception: {ex.Message}");
}
}
public class FileAppender : ILogAppender
{
private readonly string _filePath;
public FileAppender(string filePath) => _filePath = filePath;
public void Append(LogLevel level, string message, Exception? ex)
{
var line = $"[{DateTime.UtcNow:HH:mm:ss}] [{level}] {message}";
if (ex != null) line += $"\n Exception: {ex.Message}\n Stack: {ex.StackTrace}";
// 模拟写入文件
Console.WriteLine($"[File] {_filePath}: {line}");
}
}
public enum LogLevel { Debug, Info, Warning, Error, Fatal }
// 抽象 — 日志记录器
public abstract class Logger
{
protected ILogAppender Appender { get; }
protected LogLevel MinLevel { get; set; }
protected Logger(ILogAppender appender, LogLevel minLevel = LogLevel.Debug)
{
Appender = appender;
MinLevel = minLevel;
}
public void Debug(string msg) => Log(LogLevel.Debug, msg);
public void Info(string msg) => Log(LogLevel.Info, msg);
public void Warning(string msg) => Log(LogLevel.Warning, msg);
public void Error(string msg, Exception? ex = null) => Log(LogLevel.Error, msg, ex);
public void Fatal(string msg, Exception? ex = null) => Log(LogLevel.Fatal, msg, ex);
protected abstract void Log(LogLevel level, string message, Exception? ex = null);
}
public class ApplicationLogger : Logger
{
public ApplicationLogger(ILogAppender appender) : base(appender, LogLevel.Info) { }
protected override void Log(LogLevel level, string message, Exception? ex = null)
{
if (level < MinLevel) return;
Appender.Append(level, message, ex);
}
}
// 使用 — 同一个日志器可以切换不同的输出目标
var consoleLogger = new ApplicationLogger(new ConsoleAppender());
consoleLogger.Info("应用启动");
consoleLogger.Error("数据库连接失败", new Exception("Connection timeout"));
var fileLogger = new ApplicationLogger(new FileAppender("app.log"));
fileLogger.Info("应用启动");桥接模式的高级应用
跨平台 UI 桥接
// 实现接口 — 平台特定绘制
public interface IPlatformRenderer
{
void DrawButton(double x, double y, double width, double height, string text);
void DrawText(double x, double y, string text, int fontSize);
void DrawImage(double x, double y, string imagePath);
void DrawRectangle(double x, double y, double width, double height, string color);
}
// Windows 平台绘制
public class WindowsRenderer : IPlatformRenderer
{
public void DrawButton(double x, double y, double w, double h, string text)
=> Console.WriteLine($"[Win32] Button({x},{y},{w}x{h}) \"{text}\"");
public void DrawText(double x, double y, string text, int fontSize)
=> Console.WriteLine($"[Win32] Text({x},{y}) \"{text}\" {fontSize}px");
public void DrawImage(double x, double y, string path)
=> Console.WriteLine($"[Win32] Image({x},{y}) {path}");
public void DrawRectangle(double x, double y, double w, double h, string color)
=> Console.WriteLine($"[Win32] Rect({x},{y},{w}x{h}) {color}");
}
// Linux/Mac 平台绘制
public class LinuxRenderer : IPlatformRenderer
{
public void DrawButton(double x, double y, double w, double h, string text)
=> Console.WriteLine($"[GTK] Button({x},{y},{w}x{h}) \"{text}\"");
public void DrawText(double x, double y, string text, int fontSize)
=> Console.WriteLine($"[GTK] Text({x},{y}) \"{text}\" {fontSize}px");
public void DrawImage(double x, double y, string path)
=> Console.WriteLine($"[GTK] Image({x},{y}) {path}");
public void DrawRectangle(double x, double y, double w, double h, string color)
=> Console.WriteLine($"[GTK] Rect({x},{y},{w}x{h}) {color}");
}
// 抽象 — UI 控件
public abstract class UIComponent
{
protected IPlatformRenderer Renderer { get; }
protected double X { get; set; }
protected double Y { get; set; }
protected UIComponent(IPlatformRenderer renderer, double x, double y)
{
Renderer = renderer; X = x; Y = y;
}
public abstract void Draw();
}
public class Button : UIComponent
{
private readonly string _text;
private readonly double _width, _height;
public Button(IPlatformRenderer renderer, double x, double y,
string text, double width = 100, double height = 36)
: base(renderer, x, y)
{
_text = text; _width = width; _height = height;
}
public override void Draw()
{
Renderer.DrawRectangle(X - 2, Y - 2, _width + 4, _height + 4, "#666");
Renderer.DrawButton(X, Y, _width, _height, _text);
}
}
public class Label : UIComponent
{
private readonly string _text;
private readonly int _fontSize;
public Label(IPlatformRenderer renderer, double x, double y,
string text, int fontSize = 14)
: base(renderer, x, y)
{
_text = text; _fontSize = fontSize;
}
public override void Draw()
{
Renderer.DrawText(X, Y, _text, _fontSize);
}
}
// 使用 — 同一套 UI 代码,不同平台渲染
var winRenderer = new WindowsRenderer();
var linuxRenderer = new LinuxRenderer();
// Windows 版本
var winButton = new Button(winRenderer, 10, 10, "确定", 80, 32);
var winLabel = new Label(winRenderer, 10, 50, "请选择操作", 16);
winButton.Draw();
winLabel.Draw();
// Linux 版本 — 只需要替换 Renderer
var linuxButton = new Button(linuxRenderer, 10, 10, "确定", 80, 32);
var linuxLabel = new Label(linuxRenderer, 10, 50, "请选择操作", 16);
linuxButton.Draw();
linuxLabel.Draw();持久化层桥接
// 实现接口 — 数据库访问
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}
// SQL Server 实现
public class SqlRepository<T> : IRepository<T> where T : class
{
private readonly string _connectionString;
public SqlRepository(string connectionString) => _connectionString = connectionString;
public async Task<T?> GetByIdAsync(int id)
{
Console.WriteLine($"[SQL Server] 查询 {typeof(T).Name} Id={id}");
await Task.CompletedTask;
return default;
}
public async Task<IEnumerable<T>> GetAllAsync()
{
Console.WriteLine($"[SQL Server] 查询所有 {typeof(T).Name}");
return await Task.FromResult(Enumerable.Empty<T>());
}
public async Task<T> AddAsync(T entity)
{
Console.WriteLine($"[SQL Server] 插入 {typeof(T).Name}");
await Task.CompletedTask;
return entity;
}
public async Task UpdateAsync(T entity)
{
Console.WriteLine($"[SQL Server] 更新 {typeof(T).Name}");
await Task.CompletedTask;
}
public async Task DeleteAsync(int id)
{
Console.WriteLine($"[SQL Server] 删除 {typeof(T).Name} Id={id}");
await Task.CompletedTask;
}
}
// MongoDB 实现
public class MongoRepository<T> : IRepository<T> where T : class
{
private readonly string _connectionString;
public MongoRepository(string connectionString) => _connectionString = connectionString;
public async Task<T?> GetByIdAsync(int id)
{
Console.WriteLine($"[MongoDB] 查询 {typeof(T).Name} Id={id}");
await Task.CompletedTask;
return default;
}
public async Task<IEnumerable<T>> GetAllAsync()
{
Console.WriteLine($"[MongoDB] 查询所有 {typeof(T).Name}");
return await Task.FromResult(Enumerable.Empty<T>());
}
public async Task<T> AddAsync(T entity)
{
Console.WriteLine($"[MongoDB] 插入 {typeof(T).Name}");
await Task.CompletedTask;
return entity;
}
public async Task UpdateAsync(T entity)
{
Console.WriteLine($"[MongoDB] 更新 {typeof(T).Name}");
await Task.CompletedTask;
}
public async Task DeleteAsync(int id)
{
Console.WriteLine($"[MongoDB] 删除 {typeof(T).Name} Id={id}");
await Task.CompletedTask;
}
}
// 业务服务 — 依赖抽象 IRepository<T>
public class UserService
{
private readonly IRepository<User> _repository;
public UserService(IRepository<User> repository) => _repository = repository;
public async Task<User?> GetUser(int id) => await _repository.GetByIdAsync(id);
public async Task AddUser(User user) => await _repository.AddAsync(user);
}
// DI 注册 — 运行时决定使用哪种存储
// builder.Services.AddScoped<IRepository<User>>(_ =>
// new SqlRepository<User>(configuration.GetConnectionString("Default")));运行时切换实现
// 运行时动态切换桥接实现
public class SwitchableRenderer
{
private IRenderer _currentRenderer;
private readonly Dictionary<string, IRenderer> _renderers = new();
public SwitchableRenderer()
{
_renderers["html"] = new HtmlRenderer();
_renderers["markdown"] = new MarkdownRenderer();
_renderers["csv"] = new CsvRenderer();
_currentRenderer = _renderers["html"];
}
public void SwitchTo(string format)
{
if (_renderers.TryGetValue(format, out var renderer))
_currentRenderer = renderer;
else
throw new ArgumentException($"不支持的格式: {format}");
}
public string RenderReport(string title, List<string[]> rows)
{
var sb = new StringBuilder();
sb.AppendLine(_currentRenderer.RenderHeader(title));
foreach (var row in rows)
sb.AppendLine(_currentRenderer.RenderRow(row));
sb.Append(_currentRenderer.RenderFooter());
return sb.ToString();
}
}
// 使用 — 同一份数据,不同格式输出
var renderer = new SwitchableRenderer();
var data = new List<string[]>
{
new[] { "笔记本", "10", "50000" },
new[] { "手机", "25", "125000" }
};
Console.WriteLine(renderer.RenderReport("销售报表", data)); // HTML
renderer.SwitchTo("markdown");
Console.WriteLine(renderer.RenderReport("销售报表", data)); // Markdown
renderer.SwitchTo("csv");
Console.WriteLine(renderer.RenderReport("销售报表", data)); // CSV桥接模式 vs 适配器模式 vs 装饰器模式
桥接模式 适配器模式 装饰器模式
+--------+ +--------+ +--------+
| 分离 | | 转换 | | 增强 |
| 维度 | | 接口 | | 功能 |
+--------+ +--------+ +--------+
| 设计时 | | 事后 | | 运行时 |
| 决策 | | 补救 | | 添加 |
+--------+ +--------+ +--------+
| 两个 | | 一个 | | 同一个 |
| 独立 | | 不兼容 | | 接口 |
| 层次 | | 接口 | | 包装 |
+--------+ +--------+ +--------+最佳实践
- 识别独立变化维度:在设计初期分析需求,找出可能独立变化的维度。
- 优先使用组合:当发现类层次中出现"类爆炸"趋势时,考虑用桥接重构。
- 抽象和实现都可以扩展:新增抽象类型不影响实现,新增实现不影响抽象。
- 实现接口要稳定:Implementor 接口应该在设计初期就确定,频繁变更会导致桥接失效。
- 运行时切换实现:桥接允许在运行时替换实现,善用这个特性。
优点
缺点
总结
桥接模式将多维变化的场景拆分为抽象和实现两个独立层次,通过组合而非继承关联。关键识别标志:当一个类有多个独立变化的维度(如消息类型 x 发送渠道、报表类型 x 渲染格式),使用桥接避免 m x n 的类爆炸。建议在设计初期识别出独立变化维度,提前应用桥接模式。
桥接模式的本质价值在于:当你发现系统中存在两个或多个独立变化的维度,且这些维度的组合会导致类数量爆炸时,通过将维度分离为独立的继承层次,用组合关联它们,使系统更灵活、更易维护。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《桥接模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《桥接模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《桥接模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《桥接模式》最大的收益和代价分别是什么?
