设计模式-桥接模式

文章目录

  • 一、概述
    • [1.1 结构与角色](#1.1 结构与角色)
    • [1.2 适用场景](#1.2 适用场景)
  • 二、实现方式
    • [2.1 基础实现](#2.1 基础实现)
    • [2.2 扩展维度演示](#2.2 扩展维度演示)
    • [2.3 实际应用示例------消息发送系统](#2.3 实际应用示例——消息发送系统)
    • [2.4 类爆炸对比](#2.4 类爆炸对比)
  • [三、桥接模式 vs 适配器模式](#三、桥接模式 vs 适配器模式)
    • [3.1 意图对比](#3.1 意图对比)
    • [3.2 结构对比](#3.2 结构对比)
    • [3.3 代码对比](#3.3 代码对比)
    • [3.4 选择指南](#3.4 选择指南)
  • 四、总结

一、概述

在软件开发中,经常会遇到这样的场景:一个类存在两个独立变化的维度 ,比如一个"图形"类既包含形状(圆形、矩形、三角形),又包含颜色(红色、绿色、蓝色)。如果采用传统的多层继承方式,每增加一种形状或颜色,就需要创建一个新的子类,导致类的数量呈指数级增长
Shape
RedCircle
GreenCircle
BlueCircle
RedRectangle
GreenRectangle
BlueRectangle

6 个子类只是 2 种形状 × 3 种颜色的组合。如果再增加"三角形"和"黄色",子类数量将激增到 12 个。这就是所谓的类爆炸问题。

桥接模式(Bridge Pattern)正是为了解决这个问题而诞生的------它将抽象部分实现部分分离,使它们可以独立变化,从而避免使用多层继承造成的类爆炸。

生活中的桥接例子:

  • 手机与APP:手机品牌(华为、苹果、小米)和应用程序(微信、抖音、淘宝)是两个独立变化的维度,APP 不需要为每个手机品牌单独开发
  • 电器与开关:电器(灯、风扇、电视)和开关(拉线开关、按钮开关、遥控开关)可以独立变化,通过桥接模式将两者解耦
  • 日志与输出目标:日志格式(简单格式、详细格式)和输出目标(文件、数据库、控制台)可以独立扩展

核心:将抽象部分与实现部分分离,使它们都可以独立变化

1.1 结构与角色

桥接模式包含以下角色:
持有
继承
实现
实现
委托调用
Client 客户端
Abstraction 抽象类
Implementor 实现接口
RefinedAbstraction 扩展抽象类
ConcreteImplementorA 具体实现A
ConcreteImplementorB 具体实现B

  • Abstraction(抽象类):定义抽象部分的接口,并持有对实现接口的引用,通过组合关系桥接到实现部分
  • RefinedAbstraction(扩展抽象类):继承抽象类,扩展抽象部分的接口
  • Implementor(实现接口):定义实现部分的接口,不一定要与抽象部分的接口完全一致
  • ConcreteImplementor(具体实现):实现实现接口的具体类
  • Client(客户端):通过抽象类与对象交互

1.2 适用场景

  • 一个类存在两个独立变化的维度,且两个维度都需要扩展
  • 不希望使用多层继承导致类爆炸的场景
  • 需要在抽象化和具体化之间增加灵活性,避免两个层次之间的永久绑定
  • 想要对客户程序隐藏实现部分的细节

二、实现方式

桥接模式的核心实现思路是:将抽象部分 (如形状)和实现部分(如颜色)分别定义为独立的继承体系,再通过组合关系将两者桥接起来。

2.1 基础实现

以"图形与颜色"为例,形状为抽象部分,颜色为实现部分:
持有
继承
继承
实现
实现
实现
委托调用
客户端
Shape 抽象类
Color 实现接口
Circle 圆形
Rectangle 矩形
Red 红色
Green 绿色
Blue 蓝色

(1)实现接口------颜色

java 复制代码
/**
 * 实现接口:颜色
 */
public interface Color {

    /**
     * 填充颜色
     */
    String fill();
}

(2)具体实现------红色、绿色、蓝色

java 复制代码
/**
 * 具体实现:红色
 */
public class Red implements Color {

    @Override
    public String fill() {
        return "红色";
    }
}

/**
 * 具体实现:绿色
 */
public class Green implements Color {

    @Override
    public String fill() {
        return "绿色";
    }
}

/**
 * 具体实现:蓝色
 */
public class Blue implements Color {

    @Override
    public String fill() {
        return "蓝色";
    }
}

(3)抽象类------图形

java 复制代码
/**
 * 抽象类:图形
 * 持有 Color 实现接口的引用,将颜色操作委托给 Color 对象
 */
public abstract class Shape {

    protected Color color;

    public Shape(Color color) {
        this.color = color;
    }

    /**
     * 绘制图形
     */
    public abstract void draw();
}

(4)扩展抽象类------圆形、矩形

java 复制代码
/**
 * 扩展抽象类:圆形
 */
public class Circle extends Shape {

    public Circle(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.println("绘制" + color.fill() + "圆形");
    }
}

/**
 * 扩展抽象类:矩形
 */
public class Rectangle extends Shape {

    public Rectangle(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.println("绘制" + color.fill() + "矩形");
    }
}

(5)客户端调用

java 复制代码
public class BridgeDemo {
    public static void main(String[] args) {
        // 红色圆形
        Shape redCircle = new Circle(new Red());
        redCircle.draw();
        // 绘制红色圆形

        // 绿色矩形
        Shape greenRectangle = new Rectangle(new Green());
        greenRectangle.draw();
        // 绘制绿色矩形

        // 蓝色圆形
        Shape blueCircle = new Circle(new Blue());
        blueCircle.draw();
        // 绘制蓝色圆形
    }
}

关键点 :抽象类 Shape 通过组合持有 Color 接口的引用,将颜色操作委托给 Color 对象。形状和颜色可以独立扩展------新增形状不需要修改颜色类,新增颜色也不需要修改形状类。

2.2 扩展维度演示

桥接模式的最大优势在于两个维度可以独立扩展,下面演示如何新增一个维度:

(1)新增颜色------黄色

java 复制代码
/**
 * 具体实现:黄色
 * 新增颜色,无需修改任何已有的形状类
 */
public class Yellow implements Color {

    @Override
    public String fill() {
        return "黄色";
    }
}

(2)新增形状------三角形

java 复制代码
/**
 * 扩展抽象类:三角形
 * 新增形状,无需修改任何已有的颜色类
 */
public class Triangle extends Shape {

    public Triangle(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.println("绘制" + color.fill() + "三角形");
    }
}

(3)自由组合

java 复制代码
public class BridgeExtendDemo {
    public static void main(String[] args) {
        // 黄色三角形------新形状 + 新颜色
        Shape yellowTriangle = new Triangle(new Yellow());
        yellowTriangle.draw();
        // 绘制黄色三角形

        // 黄色圆形------旧形状 + 新颜色
        Shape yellowCircle = new Circle(new Yellow());
        yellowCircle.draw();
        // 绘制黄色圆形

        // 蓝色三角形------新形状 + 旧颜色
        Shape blueTriangle = new Triangle(new Blue());
        blueTriangle.draw();
        // 绘制蓝色三角形
    }
}

对比:如果使用多层继承,3 种形状 × 4 种颜色 = 12 个子类;而使用桥接模式,只需要 3 个形状类 + 4 个颜色类 = 7 个类。每新增一种形状只需 1 个类,每新增一种颜色也只需 1 个类。

2.3 实际应用示例------消息发送系统

以一个消息发送系统为例,消息类型(普通消息、加急消息、特急消息)和发送方式(邮件发送、短信发送、微信发送)是两个独立变化的维度:
持有
继承
继承
实现
实现
实现
委托调用
客户端
Message 抽象消息类
MessageSender 实现接口
CommonMessage 普通消息
UrgencyMessage 加急消息
EmailSender 邮件发送
SmsSender 短信发送
WechatSender 微信发送

(1)实现接口------消息发送器

java 复制代码
/**
 * 实现接口:消息发送器
 */
public interface MessageSender {

    /**
     * 发送消息
     *
     * @param message 消息内容
     */
    void send(String message);
}

(2)具体实现------邮件、短信、微信发送器

java 复制代码
/**
 * 具体实现:邮件发送器
 */
public class EmailSender implements MessageSender {

    @Override
    public void send(String message) {
        System.out.println("【邮件】发送消息:" + message);
    }
}

/**
 * 具体实现:短信发送器
 */
public class SmsSender implements MessageSender {

    @Override
    public void send(String message) {
        System.out.println("【短信】发送消息:" + message);
    }
}

/**
 * 具体实现:微信发送器
 */
public class WechatSender implements MessageSender {

    @Override
    public void send(String message) {
        System.out.println("【微信】发送消息:" + message);
    }
}

(3)抽象类------消息

java 复制代码
/**
 * 抽象类:消息
 * 持有 MessageSender 实现接口的引用
 */
public abstract class Message {

    protected MessageSender sender;

    public Message(MessageSender sender) {
        this.sender = sender;
    }

    /**
     * 发送消息
     *
     * @param message 消息内容
     */
    public abstract void sendMessage(String message);
}

(4)扩展抽象类------普通消息、加急消息

java 复制代码
/**
 * 扩展抽象类:普通消息
 */
public class CommonMessage extends Message {

    public CommonMessage(MessageSender sender) {
        super(sender);
    }

    @Override
    public void sendMessage(String message) {
        sender.send("【普通】" + message);
    }
}

/**
 * 扩展抽象类:加急消息
 */
public class UrgencyMessage extends Message {

    public UrgencyMessage(MessageSender sender) {
        super(sender);
    }

    @Override
    public void sendMessage(String message) {
        sender.send("【加急】" + message);
    }
}

(5)客户端调用

java 复制代码
public class MessageBridgeDemo {
    public static void main(String[] args) {
        // 普通消息 ------ 通过邮件发送
        Message commonEmail = new CommonMessage(new EmailSender());
        commonEmail.sendMessage("系统维护通知");
        // 【邮件】发送消息:【普通】系统维护通知

        // 加急消息 ------ 通过短信发送
        Message urgencySms = new UrgencyMessage(new SmsSender());
        urgencySms.sendMessage("服务器宕机告警");
        // 【短信】发送消息:【加急】服务器宕机告警

        // 加急消息 ------ 通过微信发送
        Message urgencyWechat = new UrgencyMessage(new WechatSender());
        urgencyWechat.sendMessage("数据库连接异常");
        // 【微信】发送消息:【加急】数据库连接异常
    }
}

优势体现 :如果未来需要新增"钉钉发送器",只需创建一个 DingTalkSender 实现类,无需修改任何消息类;如果需要新增"特急消息",只需创建一个 EmergencyMessage 继承 Message,无需修改任何发送器类。

2.4 类爆炸对比

以 3 种形状 × 4 种颜色为例:

方式 类的数量 新增一种形状 新增一种颜色
多层继承 12(3×4) +4 个子类 +3 个子类
桥接模式 7(3+4) +1 个类 +1 个类

维度越多、每个维度的种类越多,桥接模式的优势越明显。假设有 n 个维度,每个维度有 m 种变化,多层继承需要 (m^n) 个子类,而桥接模式只需要 (n \times m) 个类。


三、桥接模式 vs 适配器模式

桥接模式和适配器模式都属于结构型模式,而且都涉及"接口"和"组合",但它们的意图和使用场景截然不同。

关于适配器模式可参见博客:设计模式-适配器模式

3.1 意图对比

对比维度 桥接模式 适配器模式
核心意图 分离抽象与实现,使两者独立变化 将一个接口转换为另一个接口,使不兼容的类协同工作
解决的问题 类爆炸(多层继承导致的子类激增) 接口不兼容
设计时机 设计阶段(预先规划) 事后补救(已有代码不兼容)
关系性质 抽象与实现之间是组合关系,两者独立变化 适配器与被适配者之间是包装关系,接口转换
变化维度 两个维度同时独立扩展 通常只关心接口的统一

3.2 结构对比

适配器模式
持有/继承
Target
Adapter
Adaptee
桥接模式
持有组合
Abstraction
Implementor
RefinedAbstraction
ConcreteImplementor

桥接模式:Abstraction 通过组合持有 Implementor 的引用,两者是平等的合作关系,可以独立扩展。

适配器模式:Adapter 包装 Adaptee,将 Adaptee 的接口转换为 Target 接口,Adaptee 是已有的、不可修改的。

3.3 代码对比

以"日志系统"为例,对比两种模式的应用方式:

桥接模式的用法------日志格式与输出目标独立变化:

java 复制代码
// 实现接口:输出目标
public interface LogOutput {
    void output(String message);
}

// 具体实现:文件输出
public class FileOutput implements LogOutput {
    @Override
    public void output(String message) {
        System.out.println("写入文件:" + message);
    }
}

// 抽象类:日志格式
public abstract class Logger {
    protected LogOutput output;

    public Logger(LogOutput output) {
        this.output = output;
    }

    public abstract void log(String message);
}

// 扩展抽象:简单日志
public class SimpleLogger extends Logger {
    public SimpleLogger(LogOutput output) {
        super(output);
    }

    @Override
    public void log(String message) {
        output.output("[INFO] " + message);
    }
}

// 客户端:自由组合
Logger logger = new SimpleLogger(new FileOutput());
logger.log("系统启动");

适配器模式的用法------已有日志框架接口不兼容:

java 复制代码
// 目标接口:项目期望的日志接口
public interface AppLogger {
    void info(String message);
}

// 被适配者:第三方日志框架(接口不兼容,无法修改)
public class ThirdPartyLogger {
    public void writeLog(int level, String message) {
        System.out.println("Level=" + level + " : " + message);
    }
}

// 适配器:将第三方日志框架适配为项目接口
public class LoggerAdapter implements AppLogger {

    private final ThirdPartyLogger thirdPartyLogger;

    public LoggerAdapter(ThirdPartyLogger thirdPartyLogger) {
        this.thirdPartyLogger = thirdPartyLogger;
    }

    @Override
    public void info(String message) {
        thirdPartyLogger.writeLog(1, message);
    }
}

// 客户端:通过适配器使用第三方框架
AppLogger logger = new LoggerAdapter(new ThirdPartyLogger());
logger.info("系统启动");

关键区别:桥接模式是预先设计,将两个维度解耦以支持独立扩展;适配器模式是事后补救,将不兼容的接口转换为兼容的接口。

3.4 选择指南

场景 选择
一个类有两个独立变化的维度 桥接模式
需要在抽象与实现之间增加灵活性 桥接模式
已有类的接口与需求不匹配 适配器模式
需要复用第三方库或遗留代码 适配器模式
需要同时支持多种实现且可以切换 桥接模式

四、总结

桥接模式的核心思想是将抽象部分与实现部分分离,使它们都可以独立变化,从而避免多层继承造成的类爆炸问题。

优点:

  • 分离抽象与实现:两个维度独立变化,互不影响
  • 避免类爆炸:用组合代替继承,大幅减少子类数量
  • 符合开闭原则:新增形状或颜色只需新增一个类,无需修改已有代码
  • 符合合成复用原则:使用组合代替继承,降低耦合度
  • 提高可扩展性:两个维度可以独立扩展,灵活组合

缺点:

  • 增加系统复杂度:需要额外定义接口和抽象类,增加了类的数量
  • 需要正确识别维度:必须准确识别系统中独立变化的维度,否则会增加理解难度
  • 过度设计风险:如果两个维度不需要独立变化,使用桥接模式反而增加了不必要的复杂度

适用场景:

  • 一个类存在两个独立变化的维度,且两个维度都需要扩展
  • 不希望使用多层继承导致类数量激增
  • 需要在抽象化和具体化之间增加灵活性
  • 需要动态切换实现方式

桥接 vs 适配器 :桥接模式是设计阶段的决策,意在将抽象与实现解耦以支持独立扩展;适配器模式是事后补救的措施,意在将不兼容的接口转换为兼容的接口。两者虽然都使用了组合,但意图截然不同。


参考博客:

桥接模式 | 菜鸟教程:https://www.runoob.com/design-pattern/bridge-pattern.html

相关推荐
雪度娃娃3 小时前
结构型设计模式——装饰模式
设计模式·装饰器模式
sensen_kiss3 小时前
CPT304 SoftwareEngineeringII 软件工程 2 Pt.4 设计模式(下)
设计模式·软件工程
多加点辣也没关系4 小时前
设计模式-适配器模式
设计模式
Forget the Dream5 小时前
基于适配器模式的 Axios 封装实践
设计模式·typescript·axios·适配器模式
Java面试题总结5 小时前
【设计模式03】使用模版模式+责任链模式优化实战
设计模式·责任链模式
庞轩px5 小时前
Redis工具类重构——从臃肿到优雅的门面模式实践
数据库·redis·设计模式·重构·门面模式·可扩展性·可维护性
Supersist20 小时前
【设计模式03】使用模版模式+责任链模式优化实战
后端·设计模式·代码规范
geovindu21 小时前
go: Interpreter Pattern
开发语言·设计模式·golang·解释器模式
workflower1 天前
从拿订单到看方向
大数据·人工智能·设计模式·机器人·动态规划