设计模式之【桥接模式】,多用组合少用继承

2023-05-16

文章目录

  • 一、什么是桥接模式
    • 1、使用场景
    • 2、代理、桥接、装饰器、适配器 4 种设计模式的区别
    • 3、桥接模式的优缺点
    • 4、桥接模式的四种角色
  • 二、实例
    • 桥接模式优化代码
  • 三、源码中使用的桥接模式
    • 1、桥接模式在JDBC中的应用

一、什么是桥接模式

桥接模式(Bridge Pattern)也称为桥梁模式、接口(Interface)模式或柄体(Handle and Body)模式,是将抽象部分与它的具体实现部分分离,使它们都可以独立地变化,属于结构型模式。

在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”

关于桥接模式,很多书籍、资料中,还有另外一种理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于,我们之前讲过的“组合优于继承”设计原则。

桥接模式主要目的是通过组合的方式建立两个类之间的联系,而不是继承。但又类似于多重继承方案,但是多重继承方案往往违背了类的单一职责原则,其复用性比较差,桥接模式是比多重继承更好的替代方案。桥接模式的核心在于解耦抽象和实现。

注:此处的抽象并不是指抽象类或接口这种高层概念,实现也不是继承或接口实现。抽象与实现其实指的是两种独立变化的维度。其中,抽象包含实现,因此,一个抽象类的变化可能涉及到多种维度的变化导致的。

1、使用场景

当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时

当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。

当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

(1)JDBC驱动程序
(2)银行转账系统
转账分类:网上转账、柜台转账、ATM转账;
转账用户类型:普通用户、银卡用户、金卡用户
(3)消息管理
消息类型:即时消息、延时消息
消息分类:手机短信、邮件消息、QQ消息

2、代理、桥接、装饰器、适配器 4 种设计模式的区别

代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。

尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。这里我就简单说一下它们之间的区别。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

3、桥接模式的优缺点

优点:

  • 实现了抽象和实现部分的分离,从而极大地提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
  • 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
  • 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
  • 符合开闭原则,符合合成复用原则。

缺点:

  • 增加了系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
  • 需要正确地识别系统中两个独立变化的维度(抽象和实现),因此其使用范围有一定的局限性,即需要有这样的应用场景。

4、桥接模式的四种角色

桥接模式包含四种角色:

  • 抽象(Abstraction):该类持有一个对实现角色的引用,抽象角色中的方法需要实现角色来实现。抽象角色一般为抽象类(构造函数规定子类要传入一个实现对象);
  • 修正对象(RefinedAbstraction):Abstraction的具体实现,对Abstraction的方法进行完善和扩展;
  • 实现(Implementor):确定实现维度的基本操作,提供给Abstraction使用。该类一般为接口或抽象类;
  • 具体实现(ConcreteImplementor):Implementor的具体实现。

二、实例

需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。

如果我们不使用桥接模式,我们会定义操作系统接口、视频接口,然后定义各自的子接口,最后两两互相实现,最终是这样的:
在这里插入图片描述
扩展性问题(类爆炸)很明显,如果再增加操作系统、视频格式,最终的实现类会成倍增加。

解决方案就是桥接模式。

桥接模式优化代码

该播放器包含了两个维度,适合使用桥接模式。

定义视频接口及实现:

//视频文件
public interface VideoFile {
	void decode(String fileName);
}
//avi文件
public class AVIFile implements VideoFile {
	public void decode(String fileName) {
		System.out.println("avi视频文件:"+ fileName);
	}
}
//rmvb文件
public class REVBBFile implements VideoFile {
	public void decode(String fileName) {
		System.out.println("rmvb文件:" + fileName);
	}
}
//操作系统抽象
public abstract class OperatingSystemVersion {
	protected VideoFile videoFile;
	public OperatingSystemVersion(VideoFile videoFile) {
		this.videoFile = videoFile;
	}
	public abstract void play(String fileName);
}
//Windows版本
public class Windows extends OperatingSystem {
	public Windows(VideoFile videoFile) {
		super(videoFile);
	}
	public void play(String fileName) {
		videoFile.decode(fileName);
	}
}
//mac版本
public class Mac extends OperatingSystemVersion {
	public Mac(VideoFile videoFile) {
		super(videoFile);
	}
	public void play(String fileName) {
		videoFile.decode(fileName);
	}
}

测试类:

//测试类
public class Client {
	public static void main(String[] args) {
		OperatingSystem os = new Windows(new AVIFile());
		os.play("战狼3");
	}
}

桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。

如:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。
实现细节对客户透明。

三、源码中使用的桥接模式

1、桥接模式在JDBC中的应用

以mysql为例,我们使用jdbc一般这样用:

// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test");
// 得到执行sql语句的statement
PreparedStatement pst = conn.prepareStatement("select * from user");
// 返回结果
ResultSet resultSet = pst.executeQuery();

java提供了Driver接口,并没有实现,具体的实现由各大厂商完成。

mysql对Driver的实现类如下:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

当执行Class.forName(“com.mysql.jdbc.Driver”)时,会执行其静态代码块,将Driver封装成DriverInfo:

// java.sql.DriverManager#registerDriver(java.sql.Driver, java.sql.DriverAction)
public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da)
    throws SQLException {

    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }

    println("registerDriver: " + driver);

}

后续在调用DriverManager.getConnection时,我们跟踪源码:

// java.sql.DriverManager#getConnection(java.lang.String)
@CallerSensitive
public static Connection getConnection(String url)
    throws SQLException {

    java.util.Properties info = new java.util.Properties();
    return (getConnection(url, info, Reflection.getCallerClass()));
}

//  Worker method called by the public getConnection() methods.
private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
     * When callerCl is null, we should check the application's
     * (which is invoking this class indirectly)
     * classloader, so that the JDBC driver class outside rt.jar
     * can be loaded from here.
     */
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }

    println("DriverManager.getConnection(\"" + url + "\")");

    // Walk through the loaded registeredDrivers attempting to make a connection.
    // Remember the first exception that gets raised so we can reraise it.
    SQLException reason = null;

    for(DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }

        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }

    }

    // if we got here nobody could connect.
    if (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }

    println("getConnection: no suitable driver found for "+ url);
    throw new SQLException("No suitable driver found for "+ url, "08001");
}

在getConnection中又会调用各厂商自己实现的Driver的connect()方法获取连接对象,这样就巧妙地避开继承,为不同的数据库提供了相同的接口。JDBC的DriverManager就是桥:
在这里插入图片描述

我们总结一下,大致是这样一个逻辑:
1.Class.forName(“com.mysql.jdbc.Driver”); 将mysql的Driver初始化,放入DriverManager中;
2.DriverManager.getConnection 实际上是调用的mysql的Driver,获取的connect。

巧妙地使用了桥接模式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计模式之【桥接模式】,多用组合少用继承 的相关文章

  • Swagger基本示例

    类注解 span class token annotation punctuation 64 RestController span span class token annotation punctuation 64 RequestMap
  • Spring boot redis java.io.IOException: 远程主机强迫关闭了一个现有的连接

    现象 在测试环境部署的若依版本 xff0c 经常会报如下的错误 而我在开发环境基本没有遇到过 span class token number 16 span span class token operator span span class
  • thunderbird重复的已发送

    最近转到Linux下办公 xff0c 邮件客户端选用的Thunderbird 总体来说还是能适应的 但是在用的过程中发现很次发送信件后 xff0c 发件箱会有一个重复的邮件 我猜测可能是用于备份的 但是我的使用场景几乎用不到这样的功能 xf
  • linux keynav 鼠标可以扔掉了键盘控制鼠标

    我是一个vim用户 xff0c 喜欢使用键盘操作大部分的事情 无奈现在很多软件从设计之初就是为了鼠标设计的 就导致有些时候不得不使用鼠标 那么退一步讲 xff0c 能否使用键盘临时控制一下鼠标呢 xff1f 这样就可以进一步减少鼠标的使用了
  • mysql数据库的导出与导入

    在开发工作中和运维工作中 xff0c 数据库的备份与还原是很常见的工作 之前的工作中我使用oracle更多 xff0c mysql的备份工作参与的比较少 记录一下 xff0c 加深一下印象 准备工作 安装mysql客户端 xff0c 我喜欢
  • linux Windows双系统时间不一致的解决办法

    原因 电脑系统中有两个时间 xff1a 硬件时间 xff1a 保存在主板中 xff0c 信息比较少没时区 夏令时的概念 系统时间 xff1a 又系统维护 xff0c 独立于硬件时间 xff0c 拥有时区 夏令时等信息 系统时间又因为系统的不
  • 设计原则之【迪米特法则】,非礼勿近

    文章目录 一 什么是迪米特法则1 理解迪米特法则2 如何理解 高内聚 松耦合 xff1f 二 实例1 实例12 实例2 一 什么是迪米特法则 迪米特原则 xff08 Law of Demeter LoD xff09 是指一个对象应该对其他对
  • archlinux telnet客户端

    简单介绍 telnet是一种基于TCP的传统命令行远程控制协议 telnet使用非加密的通道 xff0c 因此不太安全 现在主要用于链接一些旧设备 下面这些介绍主要适用于在Arch Linux系统中配置一个telnet服务器 安装 如果只使
  • ASUS Vivobook安装linux

    使用ASUS Vivobook安装linux 会有一个小坑 如果你的linux启动不起来 xff0c 或者本来可以启动更新固件以后就不行了 这篇文章可能会给你一点帮助 BIOS设置 如果你想在这台电脑上安装linux 则Secure Boo
  • 修复grub启动 因调整分区导致linux无法启动解决过程

    linux调整分区后 xff0c 开机重启时会出现 error unknow filesystem grub rescue gt 我是windows11 43 archlinux双系统使用 这段时间用下来 xff0c 感觉使用linux更多
  • linux 扩容 新增分区

    一开始我只是尝试使用archlinux做为主要办公系统 xff0c 给系统分配的空间并不多 渐渐的也适应了archlinux感觉挺好 于是就把windows中占用的一些空间分配给archlinux用了 记录一下些过程吧 我把windows的
  • VirtualBox调整分分辨率

    以前在windows或者macOS基本都是使用vmware来玩虚拟机的 到linux环境 xff0c 想体验一下VirtualBox 结果用的时间发现分辨率都是调不出来 记录一下我的操作方法 环境 宿主机 xff1a archlinux 4
  • VirtualBox宿主机复制粘贴时有时无

    现象 首先我已经安装了增强功能 xff0c 共享剪切板选择的也是双向 虚拟机刚开机剪切板功能是正常的 可以双向复制 但是用了一段时间以后 xff0c 就会发现从宿主机得到到虚拟机就不行了 但是可以从虚拟机复制到宿主机 环境 我的宿主机是 a
  • HBuilderX运行微信小程序不报错也打不开

    我主用的是linux操作系统 xff0c 因为要开发微信小程序 所以在虚拟机中安装了一个windows7系统 环境 虚拟机中的windows7 HbuilderX 43 微信开发者工具 现象 使用HbuilderX开发uni app并想在微
  • Virtualbox虚拟机与主机相互访问

    刚从vmware切换到Virtualbox有些地方还是不太熟悉 网络连接这块就被卡了一下 xff0c 后来发现其实很简单 xff0c 是我想多了 环境 主机 xff1a archlinux 虚拟机 xff1a windows 10 软件版本
  • maven打包报内存不足,配置maven vm options

    服务器上内存所剩余不多了 xff0c 用mvn打包报内存不足 解决办法就要把vm options中内存设置小一些 mvn jvm config文件 xff1a 从 Maven 3 3 1 43 开始 xff0c 您可以通过 maven pr
  • archlinux滚动更新导致virtualbox虚拟机无法启动

    今天遇到一个奇怪的问题 xff0c 滚动更新以后 xff0c pacman Syyu 虚拟机无法正常启动了 虚拟机启动以后一直显示Starting virtual machine 并且卡在20 不动了 原因分析 我怀疑是更新系统后 xff0
  • 设计模式之【工厂模式】,创建对象原来有这么多玩法

    文章目录 一 什么是工厂模式1 工厂模式的意义2 什么时候应该用工厂模式 二 简单工厂模式1 实例 xff08 1 xff09 使用简单工厂进行优化 xff08 2 xff09 静态工厂 xff08 3 xff09 使用map来去除if x
  • archlinux安装node.js长期支持版本

    默认archlinux安装的node js版本是18 我今天在遇到项目时报错了 xff0c 项目中用到一个组件不支持18 那么如果安装 低版本的Node呢 xff1f 先在软件仓库中搜索一下 xff1a pacman Ss span cla
  • windows开机启动目录

    设置windows开机启动有很多种方法 xff0c 最简单的方法恐怕就是把快捷方式放到启动目录吧 windows开机启动目录 按下 win 43 R 打开运行输入 xff1a shell span class token operator

随机推荐

  • linux 灰度显示grayscale

    之前我在使用macOS的时候 xff0c 非常喜欢使用灰度显示界面 到底是什么原因让我有这么 变态 的需求 下面听听我的故事 xff0c 当时不重要 xff0c 可以跳过 是这样的 xff0c 因为我是程序员 xff0c 算上电脑 43 手
  • charles iOS手机抓包

    iOS手机如何抓包 下载charles https www charlesproxy com latest release download do 注册码 Registered Name span class token builtin c
  • redis cli笔记

    此篇为人个笔记 xff0c 基本是个人常用命令 xff0c 仅供参考 基础操作 redis cli redis span class token number 127 0 span 0 1 637 span class token oper
  • Jsoup通过curl Request设置header

    使用Charles时发现它有一个功能非常的方便 Copy cURL Request菜单 可以把请求中的header信息自动拼接成curl的参数 其实有很多的软件都具备这样的功能 那么如何把这些header信息直接 放到Jsoup的heade
  • 【Java题】用户数据中的身高体重收集

    题目要求 用户数据中的身高体重收集 应用程序中用户数据收集 xff0c 非常重要 xff0c 是大数据重要来源之一 在某APP场景中收集用户的身高体重数据 xff0c 身高单位 米 xff0c 体重单位kg xff0c 要求用户输入的任何数
  • archlinux i3wm通知管理

    通知管理 Dunst 是大多数桌面环境提供的通知守护程序的轻量级替代品 它非常可定制 xff0c 不依赖于任何工具包 xff0c 因此适合那些我们都喜欢定制到完美的以窗口管理器为中心的设置 官网如下 xff1a https dunst pr
  • i3wm 获取window class

    在i3wm中如果你想让一个程序固定在某个workspace中打开可以如下设置 span class token comment 打开virtual box直接进入第10个桌面 span assign span class token pun
  • xrandr修改分辨率与刷新率

    查询当前显示器信息 直接运行xrandr即可 我的执行结果如下 xff0c 信息过多 xff0c 我则把关键部分放出来 xff1a itkey 64 vivobook screenlayout xrandr span class token
  • 设计模式之【适配器模式】,两个人之间确实需要月老的搭线~

    文章目录 一 什么是适配器模式1 适配器模式使用场景2 代理 桥接 装饰器 适配器 4 种设计模式的区别3 适配器模式结构 二 类适配器1 实例 三 对象适配器1 实例 四 接口适配器1 实例 五 源码中的应用 一 什么是适配器模式 适配器
  • linux黑客帝国cmatrix

    装X神器 xff0c 黑客帝国 xff01 xff01 xff01 安装方法 我这里以archlinux为例 span class token function sudo span pacman S cmatrix 效果演示
  • mysql字段userid逗号分开保存按userid查询

    我的sql水平一般 xff0c 仅是一个笔记 无法保存是最优解 仅供参考 场景 有一张消息表 xff0c 其中有一个收信人字段中把多个用户以 分隔保存信息 我需要根据userid来查询信息 为了方便理解 xff0c 我减化一下表结构 我的表
  • windows11 + linux 蓝牙连接问题

    我主要使用Linux办公 xff0c 因为一些特殊情况需要到windows系统下测试 这时就会发现蓝牙键盘就需要重新连接 xff0c 只要切一次系统就要重新连接一次非常的麻烦 今天要多次往返这两个系统 xff0c 所以我决定解决一下这个问题
  • archlinux音量管理

    我用的i3wm 平时音量调整 xff0c 我是直接使用键盘自带的多媒体键实现的 xff0c 所以一直也懒得折腾 多媒体键盘调整音量的缺点就是无法细力度调整 xff0c 导致使用耳机听歌时 xff0c 要么声音听不到 xff0c 要么就是听不
  • git 命令行版本初始化

    假设你已经在网页上创建了一个如下的版本库 http git ycmit cn r manuli api git 然后我们需要把本地已经有的文件上传到版本库 xff0c 执行以下命令即可 span class token function g
  • mysql转SQL Server

    使用工具 Navicat 点击 工具 61 数据传输 即可完成
  • jdbc SQLServer Error: “The server selected protocol version TLS10

    最近在尝试在SQL Server 环境下开发新项目 xff0c 遇到了一些坑 xff0c 记录一下 报错信息 com span class token punctuation span microsoft span class token
  • windows11右键菜单变回windows10风格

    windows11的右键菜单会折叠一部分 xff0c 虽然美观了不少 但是总感觉效率反而更低了 xff0c 能不能设置回windows10风格的呢 xff1f 操作 Windows 43 R xff0c 输入 regedit 并按 Ente
  • C# winform使用SQLite

    本文仅是一个笔记 xff0c 仅供参考 SQLite SQLite是遵守ACID的关系数据库管理系统 xff0c 它包含在一个相对小的C程序库中 与许多其它数据库管理系统不同 xff0c SQLite不是一个客户端 服务器结构的数据库引擎
  • C# SQLite Database Locked exception

    现象 在查询时没有问题 xff0c 但是在Insert时会报数据库被锁定 原因分析 可能是代码某个地方连接着数据库 xff0c 忘记关闭了 解决办法 原理我还没有完全搞懂 xff0c 不过根据下面的写法确实解决了问题 在某个地方 xff0c
  • 设计模式之【桥接模式】,多用组合少用继承

    文章目录 一 什么是桥接模式1 使用场景2 代理 桥接 装饰器 适配器 4 种设计模式的区别3 桥接模式的优缺点4 桥接模式的四种角色 二 实例桥接模式优化代码 三 源码中使用的桥接模式1 桥接模式在JDBC中的应用 一 什么是桥接模式 桥