设计模式学习(一):Bridge桥接模式

news2024/11/27 10:34:51

一、什么是Bridge模式

Bridge模式的作用是在“类的功能层次结构”和“类的实现层次结构”之间搭建桥梁。

1.1 类的功能层次结构

主要作用就是增加新的功能。当我们要增加新的功能时,我们可以从各个层次的类中找出最符合自己需求的类,然后以它为父类编写子类,并在子类中增加新的功能。这就是“类的功能层次结构”。

假设现在有一个类Something。当我们想在Something中增加新功能时(想增加一个具体方法时),会编写一个Something类的子类(派生类),即SomethingGood类。这样就构成了一个小小的类层次结构。

这就是为了增加新功能而产生的层次结构。

如果我们要继续在SomethingGood类的基础上增加新的功能,我们可以同样地编写一个SomethingGood类的子类,即 SomethingBetter类。这样,类的层次结构就加深了。

当要增加新的功能时,我们可以从各个层次的类中找出最符合自己需求的类,然后以它为父类编写子类,并在子类中增加新的功能。这就是“类的功能层次结构”。

总而言之,父类具有基本功能,我们希望在子类中增加新的功能,这种层次结构就是“类的功能层次结构”。

需要注意的一点是:通常来说,类的层次结构关系不应当过深。

1.2 类的实现层次结构

主要作用就是增加新的实现。也就是说,这里的类的层次结构并非用于增加功能,并非用于方便我们增加新的方法,而是帮助我们实现这样的任务分担:父类通过声明抽象方法来定义接口(API ),子类通过实现具体方法来实现接口(API ),这种层次结构被称为“类的实现层次结构”。

例如,当子类Concreteclass实现了父类Abstractclass类的抽象方法时,它们之间就构成了一个小小的实现层次结构:

当我们以其他方式实现Abstractclass时,例如要实现一个AnotherConcreteclass时,类的层次结构会稍微发生一些变化:

可以看到,我们不是为了增加新的功能,而是为了增加一种新的实现方式,我们继承了Abstractclass的子类,并实现了其中的抽象方法。这就是类的实现层次结构。

1.3 为什么要使用Bridge模式

前面已经介绍了类的功能层次结构与类的实现层次结构。那么,当我们想要编写子类时,就需要先确认自己的意图:“我是要增加功能呢?还是要增加实现呢?”

当类的层次结构只有一层时,功能层次结构与实现层次结构是混杂在一个层次结构中的。这样很容易使类的层次结构变得复杂,也难以透彻地理解类的层次结构。因为自己难以确定究竟应该在类的哪一个层次结构中去增加子类。因此,我们需要将“类的功能层次结构”与“类的实现层次结构”分离为两个独立的类层次结构。当然,如果只是简单地将它们分开,两者之间必然会缺少联系。所以我们还需要在它们之间搭建一座桥梁,Bridge模式的作用就是搭建这座桥梁。

二、Bridge模式示例代码

下面我们来看一段使用了Bridge模式的示例程序。这段示例程序的功能是“显示一些东西”。乍一听好像很抽象,不过随着我们逐渐地理解这段示例程序,也就能慢慢明白它的具体作用了。在例子中一定要注意体会类的层次结构。

先看一下所有类的作用:

类图:

2.1 类的功能层次结构:Display类

Display类的功能是抽象的,负责“显示一些东西”。该类位于“类的功能层次结构”的最上层。

open、print、close这3个方法是Display类提供的接口(API),它们表示“显示的步骤”:

open是显示前的处理、print是显示处理、close是显示后的处理。

public class Display {
    private DisplayImpl impl;
    public Display(DisplayImpl impl) {
        this.impl = impl;
    }

    public void open() {
        impl.rawOpen();
    }

    public void print() {
        impl.rawPrint();
    }

    public void close() {
        impl.rawClose();
    }

    public final void display() {
        open();
        print();
        close();
    }
}

2.2 类的功能层次结构:CountDisplay类

CountDisplay类在Display类的基础上增加了一个新功能。Display类只具有“显示”的功能,CountDisplay类则具有“只显示规定的次数”的功能,这就是multiDisplay方法。CountDisplay类继承了Display类的open、print、 close方法,并使用它们来增加这个新功能。这就是“类的功能层次结构”。

public class CountDisplay extends Display{
    public CountDisplay(DisplayImpl impl) {
        super(impl);
    }

    //循环显示times次
    public void multiDisplay(int times) {
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
}

2.3 类的实现层次结构:Displaylmpl类

现在,我们来看桥的另外一侧——“类的实现层次结构”。

DisplayImpl类位于“类的实现层次结构”的最上层。

DisplayImpl类是抽象类,它声明了rawOpen、rawPrint、rawClose这3个抽象方法,它们分别与Display类的open、print、close方法相对应,进行显示前、显示、显示后处理。

public abstract class DisplayImpl {
    public abstract void rawOpen();
    public abstract void rawPrint();
    public abstract void rawClose();
}

2.4 类的实现层次结构:StringDisplaylmpl类

下面我们来看看真正的“实现”。StringDisplayImpl类是显示字符串的类。不过,它不是直接地显示字符串,而是继承了DisplayImpl类,作为其子类来使用raw0pen、rawPrint、rawClose方法进行显示。

public class StringDisplayImpl extends DisplayImpl{

    //要显示的字符串
    private String string;
    //以字节单位计算出字符串的宽度
    private int width;

    public StringDisplayImpl(String string) {
        this.string = string;
        this.width = string.getBytes().length;
    }

    @Override
    public void rawOpen() {
        printLine();
    }

    @Override
    public void rawPrint() {
        System.out.println("|" + string + "|");
    }

    @Override
    public void rawClose() {
        printLine();
    }

    private void printLine() {
        System.out.print("+");
        for (int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}

2.5 用于测试的Main类

public class Main {
    public static void main(String[] args) {
        Display d1 = new Display(new StringDisplayImpl("Hello, China."));
        Display d2 = new CountDisplay((new StringDisplayImpl("Hello, World.")));
        CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));
        d1.display();
        d2.display();
        d3.display();
        d3.multiDisplay(5);
    }
}

2.6 运行结果

+-------------+
|Hello, China.|
+-------------+
+-------------+
|Hello, World.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+

三、拓展思路的要点

3.1 分开后更容易扩展

Bridge模式的特征是将“类的功能层次结构”与“类的实现层次结构”分离开了。将类的这两个层次结构分离开有利于独立地对它们进行扩展。

当想要增加功能时,只需要在“类的功能层次结构”一侧增加类即可,不必对“类的实现层次结构”做任何修改。而且,增加后的功能可以被“所有的实现”使用。

例如,我们可以将“类的功能层次结构”应用于软件所运行的操作系统上。如果我们将某个程序中依赖于操作系统的部分划分为Windows版、Macintosh版、Unix版,那么我们就可以用Bridge模式中的“类的实现层次结构”来表现这些依赖于操作系统的部分。也就是说,我们需要编写一个定义这些操作系统的共同接口(API)的Implementor角色,然后编写Windows版、Macintosh版、Unix版的3个ConcreteImplementor角色。这样一来,无论在“类的功能层次结构”中增加多少个功能,它们都可以工作于这3个操作系统上。

3.2 继承是强关联,委托是弱关联

虽然使用“继承”很容易扩展类,但是类之间也形成了一种强关联关系,只要不修改代码,就无法改变这种关系,因此可以说它们之间形成了一种强关联关系。

如果想要很轻松地改变类之间的关系,使用继承就不适合了,因为每次改变类之间关系时都需要修改程序。这时,我们可以使用“委托”来代替“继承”关系。

上面的示例程序的Display类中使用了“委托”。Display类的impl字段保存了实现的实例。这样,类的任务就发生了转移:调用open方法会调用impl.rawOpen()方法、调用print方法会调用imp1.rawPrint()方法、调用close方法会调用impl.rawClose()方法。

也就是说,当其他类要求Display类“工作”的时候,Display类并非自己工作,而是将工作“交给impl”。这就是“委托”。

继承是强关联关系,但委托是弱关联关系。这是因为只有Display类的实例生成时,才与作为参数被传入的类构成关联。例如,在示例程序中,当Main类生成Display类和CountDisplay类的实例时,才将StringDisplayImpl的实例作为参数传递给Display类和 CountDisplay类。

如果我们不传递StringDisplayImpl类的实例,而是将其他Concretelmplementor角色的实例传递给Display类和CountDisplay类,就能很容易地改变实现。这时,发生变化的代码只有Main类,Display类和DisplayImpl类则不需要做任何修改。

继承是强关联关系,委托是弱关联关系。在设计类的时候,我们必须充分理解这一点。在Template Method模式中,也涉及到继承和委托的关系。

四、相关的设计模式

4.1 Template Method模式

在Template Method模式中使用了“类的实现层次结构”。父类调用抽象方法,而子类实现抽象方法。

4.2 Abstract Factory模式

为了能够根据需求设计出良好的ConcreteImplementor角色,有时我们会使用AbstractFactory模式。

4.3 Adapter模式

使用Bridge模式可以达到类的功能层次结构与类的实现层次结构分离的目的,并在此基础上使这些层次结构结合起来。而使用Adapter模式则可以结合那些功能上相似但是接口 (API)不同的类。

五、思考

5.1

题目:在示例程序中增加一个类,实现“显示字符串若干(随机)次”的功能。用于显示的方法是void randomDisplay(int times),它的作用是将字符串随机显示0~ times 次。要注意此时应当扩展哪个类。

答案:这是类的功能层次结构,应该继承Display类或者CountDisplay类。

5.2

题目:在示例程序中增加一个类,实现“显示文本文件的内容”的功能。要注意此时应当扩展哪个类。

答案:这是类的实现层次结构,应该继承DisplayImpl类。

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

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

相关文章

(Week 10)最小生成树(C++,prim,Kruskal,并查集)

文章目录Einstein学画画&#xff08;C&#xff0c;欧拉路&#xff09;题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示解题思路&#xff1a;并查集&#xff08;C&#xff09;[蓝桥杯 2017 国 C] 合根植物&#xff08;C&#xff0c;并查集&#xff09;题目描述输入格…

基于Java+SpringBoot+vue+element实现校园闲置物品交易网站

基于JavaSpringBootvueelement实现校园闲置物品交易网站 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目录基于JavaSpri…

Element UI 走马灯的使用

目录 走马灯是什么 原生js实现 Element UI的走马灯使用 el-carousel Carousel Events el-carousel-item 走马灯是什么 在有限空间内&#xff0c;循环播放同一类型的图片、文字等内容&#xff0c;走马灯也叫轮播图。 比如 原生js实现 JS实现轮播图效果&#xff08;同时…

mysql学习总结二

聚合函数 聚合函数表示对 值的集合 进行操作的 组&#xff08;集合&#xff09;函数。 # 华为手机价格的平均值 SELECT AVG(price) FROM products WHERE brand 华为; # 计算所有手机的平均分 SELECT AVG(score) FROM products; # 手机中最低和最高分数 SELECT MAX(score) FR…

SpringBoot解决全局和局部跨域问题的两种方式

前言 在如今前后端分离的开发模式下&#xff0c;跨域是一个非常经典的问题&#xff0c;解决的方式也有很多&#xff0c;比如代理服务器&#xff0c;使用JSONP 我之前也写过一篇解决跨域问题的文章&#xff0c;感兴趣的可以参考&#xff1a;解决Vue前后端跨域问题的多种方式 …

【现代机器人学】学习笔记九:运动规划

本节和前一节 【现代机器人学】学习笔记八&#xff1a;轨迹生成 不同&#xff0c;侧重于避障的内容。有一些我认为的重要的基本的概念&#xff1a;1.路径规划是一个纯几何问题&#xff0c;寻找一条无碰撞路径&#xff0c;不涉及动力学和时间相关内容。因此路径规划是运动规划的…

请查收 | 2022 阿里妈妈技术文章回顾

新年伊始&#xff0c;万象更新&#xff01;转眼&#xff0c;阿里妈妈技术已陪伴大家走过601天~在此&#xff0c;感谢每位读者朋友的支持与关注回顾2022&#xff0c;我们分享了60篇原创技术文章、发布了1本营销科学系列白皮书、开源了1项向量召回技术方案&#xff1b;阿里妈妈营…

Netty基础入门——NIO【1】

Netty基础入门——NIO【1】 1 NIO 1.1 三大组件 1.1.1 Channel && Buffer Channle channel类似于stream&#xff0c;是读写数据的双向通道&#xff0c;而stream要么是输入要么是输出 #mermaid-svg-9w1vFFYCVQmRvHja {font-family:"trebuchet ms",verdana…

YACC移进规约冲突案例分析

总结 总结&#xff1a; bison给出的用例是发现冲突的最便捷方法。 第一种用例&#xff1a;明确用例&#xff08;一个Example&#xff09;&#xff0c;直接反应问题。第二种用例&#xff1a;混淆用例&#xff08;两个Example&#xff09;&#xff0c;解析器无法区分两条语句。…

jenkins 节点部署

1、节点注册 登陆jenkins master界面 路径&#xff1a;首页-->系统管理--> 节点管理-->新建节点&#xff08;New Node&#xff09; 插曲&#xff1a;我在新的服务器部署master节点&#xff0c;显示剩余交换空间为0B 处理方式请查看&#xff1a;Jenkins - Free Swap…

关于MCU的BootLoader的一些理解

一、关于STM32单片机IAP升级中if(((*(__IO uint32_t*)Addr_App) & 0x2FFE0000) 0x20000000)语句的理解 参考自&#xff1a;https://blog.csdn.net/weixin_45394120/article/details/122732203?spm1001.2014.3001.5502 疑问&#xff1a; 1、为什么要用Addr_App里的数据…

Web操作系统漏洞发现——工具使用总结

目录 &#xff08;一&#xff09;web层面 1、信息收集 0x01 网站源码自己开发 0x02 网站源码使用开源CMS 2、可维护Poc 0x01 pocassist 0x02 afrog 3、APP渗透 0x01 在BP上添加转发端口 0x02 Xray进行监听 0x03 触发数据 4、Goby &#xff08;二&#xff09;操作系统层…

xss.haozi靶场通关

做完xss-labs靶场后&#xff0c;再继续做这个靶场&#xff0c;感觉这个不是很难&#xff0c;毕竟在第一个靶场也获取了一些经验&#xff0c;但是这个靶场偏向技巧&#xff0c;所以还是以了解为主。 0x00: 分析&#xff1a;对我们的代码未作出限制&#xff0c;因此这里可以使用…

如何用 nodejs 进行 sha1 加密验证,微信公众号开发验证

如何用 nodejs 进行 sha1 加密验证&#xff0c;微信公众号开发验证 一、问题 今天在摆弄微信公众号的时候&#xff0c;遇到这样一个问题&#xff1a; 我的后台是 nodejs 写的&#xff0c;express 框架&#xff0c;官方开发接入的验证代码是 php 写的&#xff0c;其中有一个部…

C语言之蓝桥杯习题(3)☞暴力求解版(思路写在解题过程中)

第一题.1.问题&#xff1a;小蓝数字卡片题小蓝有很多数字卡片&#xff0c;每张卡片上都是数字0到9。 小蓝准备用这些卡片来拼一些数&#xff0c;他想从1开始拼现在小蓝手里有0到9的卡片各2021张&#xff0c;共20210张&#xff0c;请问小蓝可以从1拼到多少?2.解题过程&#xff…

【Docker】(三)使用registry远程镜像仓库管理镜像

1.前言 本系列文章记录了从0开始学习Docker的过程&#xff0c;Docker系列历史文章&#xff1a; &#xff08;一&#xff09;基本概念与安装使用 &#xff08;二&#xff09;如何使用Docker发布一个SpringBoot服务 在上一篇中留下了一个问题&#xff0c;使用Docker发布服务的方…

【开源代码 | MATLAB线性阵列仿真】

本文编辑&#xff1a;调皮哥的小助理 1、16阵元均匀线阵方向图 %8阵元均匀线阵方向图&#xff0c;来波方向为0度 clc; clear all; close all; element_num16;%阵元数为16 d_lamda1/2;%阵元间距d与波长lamda的关系 thetalinspace(-pi/2,pi/2,200); theta0[0.2 0.1];%来波方向 w…

systemd wsl 测试笔记

文章目录systemd 简介WSL systemdsystemctljournalctlhello serviceSleep 与 Timeout 测试Requires 测试After 测试systemd 简介 Linux 从关闭到运行, 完整的启动和启动过程有三个主要部分: 硬件启动(Hardware boot): 初始化系统硬件Linux 引导(Linux boot): 加载 Linux 内核&…

基于ERNIELayoutPDFplumber-UIEX的多方案学术论文信息抽取

本项目链接&#xff1a;https://aistudio.baidu.com/aistudio/projectdetail/5196032?contributionType1 0.问题描述 可以参考issue&#xff1a; ERNIE-Layout在&#xff08;人名和邮箱&#xff09;信息抽取的诸多问题阐述#4031 ERNIE-Layout因为看到功能比较强大就尝试了一…

Linux安装mongodb集群整合SpringBoot

一、Mongodb集群安装 本文介绍基于mongodb的副本机制搭建集群 192.168.139.186CentOS Linux release 7.7.1908 (Core)192.168.139.187CentOS Linux release 7.7.1908 (Core)192.168.139.188CentOS Linux release 7.7.1908 (Core) 准备工作 关闭selinux&#xff0c;关闭防火墙…