万字解析设计模式之桥接模式、外观模式

一、桥接模式

1.1概述

桥接模式是一种结构型设计模式,它的作用是将抽象部分和实现部分分离开来,使它们能够独立地变化。这样,抽象部分和实现部分可以分别进行扩展,而不会相互影响。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

在桥接模式中,抽象部分和实现部分之间通过一个称为“桥”的接口进行连接。这个桥接口定义抽象部分所需要的所有方法,而实现部分则实现这些方法。这种设计方式可以使得实现部分的变化不会对抽象部分造成影响,因为抽象部分只依赖于桥接口,而不依赖于具体的实现部分。

桥接模式通常使用在需要跨越多个平台或多个产品版本的场景中。它可以提高代码的可复用性和可维护性,同时也可以使得系统更加灵活和可扩展。

现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系

我们可以发现有很多的类,假如我们再增加一个形状或再增加一种颜色,就需要创建更多的类。

试想,在一个有多种可能会变化的维度的系统中,用继承方式会造成类爆炸,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类。为了更加灵活的设计系统,我们此时可以考虑使用桥接模式。

 1.2结构

桥接(Bridge)模式包含以下主要角色:

  • 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。

 1.3实现

【例】视频播放器

需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。

类图如下:

实现化(Implementor)角色

package com.yanyu.Bridge;//视频文件
public interface VideoFile {void decode(String fileName);
}

具体实现化(Concrete Implementor)角色

package com.yanyu.Bridge;//avi文件
public class AVIFile implements VideoFile {public void decode(String fileName) {System.out.println("avi视频文件:"+ fileName);}
}
package com.yanyu.Bridge;//rmvb文件
public class REVBBFile implements VideoFile {public void decode(String fileName) {System.out.println("rmvb文件:" + fileName);}
}

 抽象化(Abstraction)角色

package com.yanyu.Bridge;//操作系统版本
public abstract class OperatingSystemVersion {protected VideoFile videoFile;public OperatingSystemVersion(VideoFile videoFile) {this.videoFile = videoFile;}public abstract void play(String fileName);
}

 扩展抽象化(Refined Abstraction)角色

package com.yanyu.Bridge;//Windows版本
public class Windows extends OperatingSystemVersion {public Windows(VideoFile videoFile) {super(videoFile);}public void play(String fileName) {videoFile.decode(fileName);}
}
package com.yanyu.Bridge;//mac版本
public class Mac extends OperatingSystemVersion {public Mac(VideoFile videoFile) {super(videoFile);}public void play(String fileName) {videoFile.decode(fileName);}
}

客户端类

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

好处:

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

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

  • 实现细节对客户透明

1.4应用场景

适合应用场景:

  • 如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。
  • 桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 你可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。

1.5JDK源码解析

桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象与实现分离,使它们可以独立变化。在桥接模式中,抽象和实现可以分别在两个不同的类层次结构中定义,而通过组合的方式将它们联系起来。这样做的好处是可以减少继承带来的耦合问题,提高系统的灵活性和可扩展性。

在 JDK 中,桥接模式的应用非常广泛,下面我们分别来看几个典型的例子:

1. JDBC

在 JDBC 中,数据访问 API 和数据库驱动实现是分离的。数据访问 API 是一组接口和类,定义了数据库操作的抽象方法和常量。而具体的数据库驱动实现则是一组不同的 jar 包,每个包对应一种数据库的不同驱动实现。这样设计的好处是可以实现代码的复用,将代码的改动范围限定在实现层次结构中,不影响客户端的使用。

2. AWT

在 AWT 中,抽象窗口工具包(Abstract Window Toolkit,简称 AWT)提供了一系列抽象类和接口,用于实现和显示图形界面。而具体的 GUI 组件实现则是由不同的操作系统提供的。比如,Windows 提供的 AWT 组件实现和 Linux 提供的 AWT 组件实现是不同的,但它们都可以通过抽象类和接口来实现统一的调用方式,从而保证了跨平台的兼容性。

3. java.util.logging

在 java.util.logging 中,抽象日志记录器(Logger)定义了一组抽象方法和常量,用于记录日志信息。而具体的日志记录实现则是通过不同的日志 Handler 实现的。比如,FileHandler、ConsoleHandler、StreamHandler 等,它们都继承自抽象类 AbstractHandler,实现了具体的日志记录方式。这样设计的好处是可以将日志记录器与具体记录方式分离,提高代码的可扩展性和灵活性。

总之,桥接模式是一种非常实用的设计模式,它可以将抽象和实现分离,使它们可以独立变化。在 JDK 中,有很多典型的桥接模式应用,这些应用不仅为我们提供了很好的学习案例,还可以帮助我们更好地理解桥接模式的思想和作用。

 二、外观模式

2.1概述

外观模式(Facade Pattern)是一种结构型设计模式,它为一组复杂的子系统接口提供了一个统一的接口,以方便客户端使用。外观模式通过将复杂的系统封装在一个简单的外观对象中,简化了客户端的调用过程,同时隐藏了系统的复杂性。

外观模式通常会定义一个简单的高层接口,这个接口封装了系统的所有复杂流程和方法调用,并将这些流程和方法调用转化为若干个简单的方法,供客户端直接调用。这样客户端就不需要了解系统的内部实现细节,也不需要知道哪些子系统需要协同工作才能完成一个请求。

总的来说,外观模式提供了一种简单的方式来使用复杂系统,并使得系统更加易于使用和维护。外观(Facade)模式是“迪米特法则”的典型应用

2.2  结构

外观(Facade)模式包含以下主要角色:

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。

2.3实现

【例】智能家电控制

小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。类图如下:

 

外观(Facade)角色

package com.yanyu.Facade;//智能音箱
public class SmartAppliancesFacade {//定义三个私有属性,分别代表三种电器设备private Light light;private TV tv;private AirCondition airCondition;//构造方法,初始化三种电器设备的对象public SmartAppliancesFacade() {light = new Light();tv = new TV();airCondition = new AirCondition();}//公共方法,根据语音指令控制电器的开关public void say(String message) {//如果语音指令包含“打开”,则调用on()方法if(message.contains("打开")) {on();//如果语音指令包含“关闭”,则调用off()方法} else if(message.contains("关闭")) {off();//否则,打印提示信息} else {System.out.println("我还听不懂你说的!!!");}}//私有方法,起床后一键开电器private void on() {//打印提示信息System.out.println("起床了");//调用三种电器设备的on()方法,分别开启灯、电视和空调light.on();tv.on();airCondition.on();}//私有方法,睡觉一键关电器private void off() {//打印提示信息System.out.println("睡觉了");//调用三种电器设备的off()方法,分别关闭灯、电视和空调light.off();tv.off();airCondition.off();}
}

定义了一个智能音箱类,作为三种电器设备的门面,提供了一个统一的接口,使得客户端可以通过语音指令来控制电器的开关,而不需要了解电器的具体实现细节。外观模式可以简化客户端与子系统之间的交互,降低系统的复杂度和耦合度

 子系统(Sub System)角色

package com.yanyu.Facade;//灯类
public class Light {public void on() {System.out.println("打开了灯....");}public void off() {System.out.println("关闭了灯....");}
}
package com.yanyu.Facade;//电视类
public class TV {public void on() {System.out.println("打开了电视....");}public void off() {System.out.println("关闭了电视....");}
}
package com.yanyu.Facade;//空调类
public class AirCondition {public void on() {System.out.println("打开了空调....");}public void off() {System.out.println("关闭了空调....");}
}

客户端类

package com.yanyu.Facade;//测试类
public class Client {public static void main(String[] args) {//创建外观对象SmartAppliancesFacade facade = new SmartAppliancesFacade();//客户端直接与外观对象进行交互facade.say("打开家电");facade.say("关闭家电");}
}
  • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

2.4应用场景

  • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

三、 桥接模式实验

任务描述

某软件公司欲开发一个数据转换工具,可以将数据库中的数据转换成多种文件格式,例如 TXT、XML、PDF 等格式,同时该工具需要支持多种不同的数据库。

本关任务:用桥接模式对模拟程序进行框架搭建,如图。

,

实现方式

  1. 明确类中独立的维度。 独立的概念可能是: 抽象/平台, 域/基础设施, 前端/后端或接口/实现。

  2. 了解客户端的业务需求, 并在抽象基类中定义它们。

  3. 确定在所有平台上都可执行的业务。 并在通用实现接口中声明抽象部分所需的业务。

  4. 为你域内的所有平台创建实现类, 但需确保它们遵循实现部分的接口。

  5. 在抽象类中添加指向实现类型的引用成员变量。 抽象部分会将大部分工作委派给该成员变量所指向的实现对象。

  6. 如果你的高层逻辑有多个变体, 则可通过扩展抽象基类为每个变体创建一个精确抽象。

  7. 客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。 此后, 客户端只需与抽象对象进行交互, 无需和实现对象打交道。

编程要求

根据提示,在右侧编辑器 Begin-End 内补充 "DataHandler.java"、"FileConvertor.java"、"PDFConvertor.java"、"XMLConvertor.java" 和 "TXTConvertor.java" 文件的代码。

测试说明

平台会自动从 xml 文件中读取内容,然后对你编写的代码进行测试:

预期输出: 从Oracle数据库中读取数据 转换成PDF格式的数据

预期输出: 从SQLServer数据库中读取数据 转换成TXT格式的数据

客户端类

package step1;import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;public class XMLUtils {//定义一个静态方法,根据传入的标签名,返回对应的类的实例对象public static Object getBean(String name) {try {//创建DOM文档对象DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = dFactory.newDocumentBuilder();Document doc;//解析XML文件,获取Document对象doc = builder.parse(new File("/data/workspace/myshixun/src/config.xml"));//获取包含类名的文本结点NodeList nl = doc.getElementsByTagName(name);Node classNode = nl.item(0).getFirstChild();//获取结点的值,即类名String cName = classNode.getNodeValue();//通过类名生成实例对象并将其返回Class c = Class.forName(cName);Object obj = c.getDeclaredConstructor().newInstance();return obj;}catch(Exception e) {//捕获并打印异常e.printStackTrace();return null;}}
}

这段代码是一个XML工具类,它使用了DOM方式来解析XML文件,获取其中的类名,并通过反射机制来创建类的实例对象。这样可以实现配置文件和代码的分离,提高代码的可维护性和可扩展性 

package step1;public class Client {public static void main(String[] args) {//创建一个抽象化角色的对象,通过XML配置文件获取其具体实现类的名称FileConvertor fileConvertor = (FileConvertor) XMLUtils.getBean("className1");//将一个实现化角色的对象注入到抽象化角色中,通过XML配置文件获取其具体实现类的名称fileConvertor.setDataHandler((DataHandler) XMLUtils.getBean("className2"));//调用抽象化角色的方法,实现对文件的转换fileConvertor.translate();}
}

 实现化角色


package step1;/********** Begin *********/
//定义一个抽象的数据处理类,作为实现化角色
public abstract class  DataHandler{//定义一个抽象的数据读取方法,由子类实现public abstract void readData();
}/********** End *********/

具体实现化角色

package step1;//定义一个从Oracle数据库中读取数据的类,继承自DataHandler类,作为具体实现化角色
public class OracleHandler extends  DataHandler{//重写父类的抽象方法,实现具体的数据读取操作@Overridepublic void readData() {System.out.println("从Oracle数据库中读取数据");}
}
package step1;public class SQLServerHandle extends DataHandler{@Overridepublic void readData() {System.out.println("从SQLServer数据库中读取数据");}
}

抽象化角色

package step1;/********** Begin *********/
//定义一个抽象的文件转换类,作为抽象化角色
public abstract class  FileConvertor{//定义一个受保护的数据处理类的引用,作为实现化角色的接口protected DataHandler handler;//定义一个公共的方法,用于设置数据处理类的对象,实现桥接的过程public void setDataHandler(DataHandler handler) {this.handler = handler;}//定义一个抽象的方法,用于转换文件,由子类实现,调用数据处理类的方法public abstract void translate();}/********** End *********/

扩展抽象类

package step1;//定义一个PDF文件转换类,继承自FileConvertor类,作为扩展抽象化角色
public class PDFConvertor extends FileConvertor{//重写父类的抽象方法,实现具体的文件转换操作@Overridepublic void translate() {/********** Begin *********///调用实现化角色的方法,读取数据handler.readData();/********** End *********///打印提示信息,表示转换成PDF格式的数据System.out.println("转换成PDF格式的数据");}
}
package step1;public class TXTConvertor extends FileConvertor{@Overridepublic void translate() {/********** Begin *********/handler.readData();/********** End *********/System.out.println("转换成TXT格式的数据");}
}
package step1;public class XMLConvertor extends FileConvertor{@Overridepublic void translate() {/********** Begin *********/handler.readData();/********** End *********/System.out.println("转换成XML格式的数据");}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

双指针算法(题目与答案讲解)

文章目录 题目移动零复写零两数之和N数之和(>2个数) 答案讲解移动零复写零两数之和N数之和 题目 力扣 移动零 1、移动零:题目链接 复写零 2、复写零:题目链接 两数之和 3、两数之和题目链接 N数之和(>2个数) 4、N数之和(三个数、四个数) 三个数:题目链接 四个数题目链接…

Windows10免安装PostgreSQL

1. PostgreSQL简介2. 下载3. 安装环境4. 安装 4.1. 初始化数据库4.2. 启动数据库4.3. 注册服务4.3. 卸载服务 1. PostgreSQL简介 PostgreSQL 是一种特性非常齐全的自由软件的对象-关系型数据库管理系统,是以加州大学计算机系开发的 POSTGRES 4.2版本为基础的对象关…

02-微服务的拆分规则和基于RestTemplate的远程调用

微服务的拆分与远程调用 创建父工程 任何分布式架构都离不开服务的拆分, 微服务也是一样 , 微服务的拆分遵守三个原则 微服务需要根据业务模块拆分,不同微服务不要重复开发相同业务每个微服务都有自己独立的数据库, 不要直接访问其他微服务的数据库微服务可以将自己的业务暴…

viple模拟器使用(三):unity模拟器中实现沿右墙迷宫算法

沿右墙迷宫算法原理 默认直行;右侧有路,则右转;前方无路,则左转。 由于unity模拟器中使用机器人移动动力控制和机器人转动角度控制来实现控制机器人前进、后退、暂停、左转、右转 。 程序编写 主机以及配置 通过内建事件初始化机…

查理·芒格之死对伯克希尔·哈撒韦公司意味着什么?

来源:猛兽财经 作者:猛兽财经 虽然查理芒格是伯克希尔哈撒韦公司首席执行官沃伦巴菲特的重要合作伙伴,但猛兽财经认为查理芒格的去世并不会对伯克希尔哈撒韦的正常运营产生太大的影响,因为该公司长期以来一直是由巴菲特主导的。 …

C/C++内存管理(含C++中new和delete的使用)

文章目录 C/C内存管理(含C中new和delete的使用)1、C/C内存分布2、C语言中动态内存管理方式:malloc/calloc/realloc/free3、C动态内存管理3.1、new/delete操作内置类型3.2、new/delete操作自定义类型 4、operator new与operator delete函数5、…

ChatGPT到底是如何运作?

自从2022年11月30日发布以来,ChatGPT一直占据着科技届的头条位置,随着苹果的创新能力下降,ChatGPT不断给大家带来震撼,2023年11月7日,首届OpenAI开发者大会在洛杉矶举行,业界普遍认为,OpenAI的开…

走近科学之《MySQL 的秘密》

走近科学之《MySQL 的秘密》 mysql 存储引擎、索引、执行计划、事务、锁、分库分表、优化 1、存储引擎(storage engines) 存储引擎规定了数据存储时的不同底层实现,如存储机制、索引、锁、事务等。 可以通过 show engines 命令查看当前服务…

【JavaSE】:接口(二)

接口 一.对学生进行排序二.Clonable接口三.抽象类和接口的区别四.Object类 一.对学生进行排序 很明显我们直接对学生进行排序是会报错的,因为编译器也不知道你需要按照名字排还是分数排,我们点击报错位置进入sort内部。 在sort内部我们可以看到它把数组转…

泛微OA对接金蝶云星空方案分享(对接场景解析)

分享金蝶云星空跟泛微OA系统集成对接的方案分享,主讲审批流程对接,表单对接的两类场景。分别是金蝶云星空发起申请和泛微发起流程审批,最终实现统一管理。 数据集成主要有以下好处: (1)数据一致性&#xf…

东南大学与OpenHarmony携手共建开源生态,技术俱乐部揭牌成立并迎来TSC专家进校园

11月25日,OpenAtom OpenHarmony(以下简称“OpenHarmony”)项目群技术指导委员会(以下简称“TSC”)与东南大学携手,于东南大学九龙湖校区金智楼一楼报告厅举办了“东南大学OpenHarmony技术俱乐部成立仪式暨OpenHarmony TSC专家进校园”活动。此次盛会标志着OpenHarmony开源社区和…

Mac M1 安装Docker打包arm64的python项目的镜像包

1、首先安装Docker,到官网下载,选择apple chip版 Docker中文网 官网 2、双击下载的dmg文件,在弹出框中之间拖拽到右边 3、打开docker,修改国内镜像源,位置在配置-DockerEngine "registry-mirrors": ["…

Go字符串类型

一、字符串 1、字符串 Go 语言里的字符串的内部实现使用 UTF-8 编码字符串带的值为双引号(")中的内容,可以在 Go 语言的源码中直接添加非ASCII 码字符 s1 : "hello" s2 : "您好" 2、字符串转义符 Go 语言的字符…

UE4 UE5 使用SVN控制

关键概念:虚幻引擎中使用SVN,帮助团队成员共享资源。 1. UE4/UE5项目文件 如果不需要编译的中间缓存,则删除: DerivedDataCache、Intermediate、Saved 三个文件夹 2.更新、上传

【linux】基本指令(中篇)

echo指令 将引号内容打印到显示屏上 输出的重定向 追加的重定向 输出的重定向 我们学习c语言的时候当以写的方式创建一个文件,就会覆盖掉该文件之前的内容 当我们以追加的方式打开文件的时候,原文件内容不会被覆盖而是追加 more指令 10.more指令…

YOLOv8独家原创改进:自研独家创新MSAM注意力,通道注意力升级,魔改CBAM

💡💡💡本文自研创新改进:MSAM(CBAM升级版):通道注意力具备多尺度性能,多分支深度卷积更好的提取多尺度特征,最后高效结合空间注意力 1)作为注意力MSAM使用; 推荐指数:五星 MSCA | 亲测在多个数据集能够实现涨点,对标CBAM。 在道路缺陷检测任务中,原始ma…

基于51单片机超声波测距汽车避障系统

**单片机设计介绍, 基于51单片机超声波测距汽车避障系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的超声波测距汽车避障系统是一种用于帮助汽车避免碰撞和发生事故的设备,以下是一个基本…

Java面试-框架篇-Mybatis

Java面试-框架篇-Mybatis MyBatis执行流程延迟加载使用及原理一, 二级缓存来源 MyBatis执行流程 读取MyBatis配置文件: mybatis-config.xml加载运行环境和映射文件构造会话工厂SqlSessionFactory会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)操作数据库的接口, Ex…

从零开始学习typescript——数据类型

数据类型 以前我们用js编写代码的时候,都是直接使用let、var、const 来定义数据类型;js会在运行时来确定数据类型,但是在ts中,可以在声明时就可以指定数据类型。如果你学过其他编程语言,比如c、java就能更好的理解了。…

HTML5原生视频播放器组件video的videocontrolslist属性详解

HTML5提供了内置的视频播放控件,其中videocontrolslist是其中一个很有用的属性。videocontrolslist属性可以用于告诉浏览器在视频播放过程中应该显示哪些默认的用户界面控件。下面我们将从几个方面来介绍videocontrolslist的详细使用。 一、启用videocontrolslist videocont…