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

news2024/11/15 14:04:57

文章目录

  • 一、什么是桥接模式
    • 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。

巧妙地使用了桥接模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/506583.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java 并发编程】Java 线程本地变量 ThreadLocal 详解

Java 线程本地变量 ThreadLocal 详解 1. ThreadLocal 简介2. ThreadLocal 的使用2.1 ThreadLocal 接口&#xff08;1&#xff09;initialValue()&#xff08;2&#xff09;get()&#xff08;3&#xff09;set(T value)&#xff08;4&#xff09;remove() 2.2 ThreadLocal 应用 …

springboot链接redis (Windows版本)

1。 下载链接 https://redis.io/download/ 2.下载完成之后傻瓜安装 3. 打开下载安装路径 3.输入cmd回车 4.依次执行以下命令 redis-server.exe redis-server.exe redis.windows.conf redis-cli shutdown exit redis-server.exe redis.windows.conf 可成功启动redis 5…

(五)Kubernetes - 手动部署(二进制方式安装)

Kubernetes - 手动部署 [ 4 ] 1 增加Master节点(高可用架构)1.1 部署Master2 Node1.1.1 安装Docker(Master1)1.1.2 启动Docker、设置开机自启(Master2)1.1.3 创建etcd证书目录(Master2)1.1.4 拷贝文件(Master1)1.1.5 删除证书(Master2)1.1.6 修改配置文件和主机名(Master2)1.1.…

在线一键解jsjiami.v6

在当今的互联网时代&#xff0c;JavaScript是web开发的核心技术之一。但是&#xff0c;为了保护JavaScript代码的安全性&#xff0c;很多开发者会使用JS加密技术。其中一个常用的JS加密工具是jsjiami.v6。 JS加密通过对JavaScript代码进行混淆、压缩、编码等多种操作&#xff…

使用crond定时跑脚本备份数据库

前言&#xff1a; 开发环境 服务器&#xff1a;centos 7&#xff08;腾讯云轻量服务器&#xff09; 数据库&#xff1a;mysql 5.7 一、crond是什么&#xff1f; crond 是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程&#xff0c;与windows下的计划任务…

车载测试-can报文解析规则实例

报文解析 报文组成 一般报文主要有以下几个参数&#xff08;比较全的情况下&#xff09; 例 解析报文时主要用到的是帧ID和帧数据 帧ID 接收到的帧ID是十六进制的形式&#xff0c;由29位标识符转换的&#xff0c;目前大多数的通信协议中都直接给出了相应的帧ID&#xff0c…

一文读懂ChatGPT(全文由ChatGPT撰写)

最近ChatGPT爆火&#xff0c;相信大家或多或少都听说过ChatGPT。到底ChatGPT是什么&#xff1f;有什么优缺点呢&#xff1f; 今天就由ChatGPT自己来给大家答疑解惑~ 全文文案来自ChatGPT&#xff01; 01 ChatGPT是什么 ChatGPT是一种基于人工智能技术的自然语言处理系统&…

光耦继电器和普通继电器的区别概述

光耦继电器和普通继电器都是电气传感器元件中的一种&#xff0c;其都能够将电能转化成机械能&#xff0c;并实现各种开关控制和保护控制。但光耦继电器与普通继电器最大的区别在于其输入电路与输出电路之间通过光电转换器件&#xff0c;而不是实现物理接触。本文将从结构、工作…

为什么越来越多的设计师开始用云渲染来渲图?

为什么越来越多的设计师开始使用 云渲染&#xff1f;小编认为可以从设计师以及云渲染平台自身这2个方向分析&#xff0c;下面一起阅读云渲染干货~ 一.针对于设计师 1.出图多&#xff0c;电脑供不应求 绘图员制作完后需要渲染给甲方确认&#xff0c;甲方要求多的又着急的话边改…

MySQL---基本查询DQL(下)(排序查询、聚合查询、分组查询、分页查询、正则表达式)

1. 排序查询 语法&#xff1a; select 字段名1&#xff0c;字段名2&#xff0c;…… from 表名 order by 字段名1 [asc|desc]&#xff0c;字段名2[asc|desc]……注意&#xff1a;asc代表升序&#xff0c;desc代表降序&#xff0c;如果不写默认升序 order by用于子句中可以支持…

CM211-1-ZG_S905L 3-B_当贝纯净桌面-线刷固件包

CM211-1-ZG_S905L 3-B_当贝纯净桌面-线刷固件包-内有教程及短接点 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&…

python中函数与类 类中的方法-静态方法/动态方法

class student():position即令def __init__(self,name,age):self.namenameself.ageagedef eat(self):passclassmethoddef cla(cls):passstaticmethoddef sta():passpassstustudent(name张三,age12) print(stu.position)stu.sta() stu.cla()# 直接使用静态和类方法 student.cla(…

第一章 程序设计基本方法

文章目录 第一章 程序设计基本方法1 程序设计语言1.1 程序设计语言1.2 编译和解释1.3 计算机编程1、为什么学习编程&#xff1f;2、编程 2 Python语言概述2.1 Python语言的发展2.2 Python最小程序 3 Python开发环境配置3.1 Python开发环境安装1、打开官网下载2、运行安装包 3.2…

CompletableFuture使用教学

CompletableFuture使用教学 一、开始一个线程异步执行不需要返回值 通过runAsync方式 //1.不加线程池方式 CompletableFuture<Void> completableFuture CompletableFuture.runAsync(() -> {System.out.println(Thread.currentThread().getName());//停顿几秒try {…

鸿蒙Hi3861学习七-Huawei LiteOS-M(信号量)

一、简介 信号量&#xff08;Semaphore&#xff09;是一种实现任务间通信的机制&#xff0c;实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。 在多任务系统中&#xff0c;各任务之间需要同步或互斥实现临界资源的保护&#xff0c;信号量功…

企业官方网站怎么申请?

在数字化时代&#xff0c;企业官方网站是展示企业形象、宣传产品和服务的重要窗口。那么&#xff0c;企业官方网站怎么申请呢&#xff1f;下面是一些简单的步骤。 1、选择合适的网站建设平台 目前市面上有许多网站建设平台&#xff0c;企业需要根据自己的需求和预算选择适合自…

Vue3学习笔记(尚硅谷)

文章目录 一、创建vue3工程1-1、使用vite创建vue3项目1-1、安装开发者工具 二、常用Composition API2-1、setup2-2、ref函数2-3、reactive函数2-4、Vue3的响应式原理2-4-1.Vue2的响应式原理2-4-3.Vue3的响应式原理 2-5、reactive对比ref2-6、setup的两个注意点2-7、计算属性与监…

Excel中创建图表的快捷方式哪些

如果你在Excel中创建了很多图表&#xff0c;你可能正在寻找加快创建和格式化速度的快捷方式。以下是一些可以用于Excel图表的有用快捷方式。 一、在新工作表上创建新图表 要在新工作表上创建新图表&#xff0c;请执行以下操作&#xff1a; ​选择要用于创建图表的数据。按F1…

域适应 Domain adaption(1)

一、定义 1、无监督域自适应 经典机器学习假设训练集和测试集来自相同的分布。 然而&#xff0c;这个假设在现实世界的应用程序中可能并不总是成立&#xff0c;例如&#xff0c;数据来源不同。 这种情况下&#xff0c;域分布之间会存在差异&#xff0c;直接将训练好的模型应…

实时数仓项目开发过程中发现的几个问题和优化点(数据接入)

1、属性值被截断的问题 在数据实时接入阶段&#xff0c;使用NIFI ExecuteScript组件生成增、改、删SQL语句&#xff0c;将SQL语句放到了attribute中(详见视频教程http://mp.weixin.qq.com/s?__bizMzIyNzkwNDE4Nw&mid2247486672&idx1&sn41793a61dc5f7ca6b6f9a34b4…