桥接模式(Bridge Pattern)

本节主要介绍结构型模式中的桥接模式 。为方便理解,从本节开始的设计模式将使用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. 实际应用建议

识别变化维度:在设计时,仔细分析系统中哪些维度会独立变化

优先使用组合:当发现需要多层继承时,考虑使用桥接模式

避免过度设计:如果变化维度不多或相对稳定,可能不需要使用桥接模式

桥接模式通过将抽象和实现分离,提供了一种比继承更灵活的设计方案,特别适合处理多维度变化的系统设计。

相关推荐
zhaokuner17 小时前
14-有界上下文-DDD领域驱动设计
java·开发语言·设计模式·架构
Geoking.20 小时前
【设计模式】抽象工厂模式(Abstract Factory)详解:一次创建“一整套产品”
设计模式·抽象工厂模式
zhaokuner1 天前
12-深层模型与重构-DDD领域驱动设计
java·开发语言·设计模式·架构
不加糖4351 天前
设计模式 -- 适配器 & 策略模式
python·设计模式
__万波__1 天前
二十三种设计模式(十九)--备忘录模式
java·设计模式·备忘录模式
killer_queen48041 天前
设计模式-观察者模式
观察者模式·设计模式
Yu_Lijing1 天前
基于C++的《Head First设计模式》笔记——观察者模式
c++·笔记·设计模式
Geoking.1 天前
【设计模式】工厂方法模式(Factory Method)详解:从简单工厂到真正的“面向扩展”
设计模式·工厂方法模式