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

一、桥接模式

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{
    //重写父类的抽象方法,实现具体的数据读取操作
    @Override
    public void readData() {
        System.out.println("从Oracle数据库中读取数据");
    }
}
package step1;

public class SQLServerHandle extends DataHandler{
    @Override
    public 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{
    
    //重写父类的抽象方法,实现具体的文件转换操作
    @Override
    public void translate() {
        /********** Begin *********/
         
        //调用实现化角色的方法,读取数据
        handler.readData();

        /********** End *********/
        //打印提示信息,表示转换成PDF格式的数据
        System.out.println("转换成PDF格式的数据");
    }
}
package step1;

public class TXTConvertor extends FileConvertor{
    
    @Override
    public void translate() {
        /********** Begin *********/
        
        handler.readData();
        /********** End *********/
        System.out.println("转换成TXT格式的数据");
    }
}
package step1;

public class XMLConvertor extends FileConvertor{
     
    @Override
    public void translate() {
        /********** Begin *********/
        
         handler.readData();
        /********** End *********/
        System.out.println("转换成XML格式的数据");
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux:wget后台下载/查看后台任务进度

1. 后台下载 使用wget -b url: wget -b http://cn.wordpress.org/wordpress-3.1-zh_CN.zip后台任务启动后,会返回两段话,第一段返回一个pid,代表这个后台任务的进程,并且我们可以kill掉这个id来终止此次下载&#x…

【Python】给出n个数,找出这n个数的最大值,最小值,和。

问题描述 给出n个数,找出这n个数的最大值,最小值,和。 样例输入 5 1 3 -2 4 5 Data 样例输出 5 -2 11 n int(input()) # 从用户输入中读取一个整数,将其赋给变量n# 从用户输入中读取一行字符串,使用空格分割字符串&a…

LED Driver数码屏应用解决方案

今天给大家介绍的产品是LED Driver,这属于电源管理类芯片,一般分为恒流驱动与恒压驱动,但是常见的就是恒流驱动,能够保持产品在驱动中提供恒定且稳定的电流。 基本概述 TM1629是一种带键盘扫描接口的LED(发光二极管显…

线程池[重点]

线程池概述 线程池就是一个可以复用线程的技术。 不使用线程池的问题 :如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。 …

2023年中国醇酸树脂涂料需求量、应用领域及市场规模前景分析[图]

醇酸树脂指多元醇和多元酸与脂肪酸经过酯化缩聚生成的高聚物,其由邻苯二甲酸酐、多元醇和脂肪酸或甘油三脂肪酸酯缩合聚合而成。醇酸树脂固化成膜后,具有耐磨性好、绝缘性佳等优势,在涂料领域应用广泛。2022年醇酸树脂产量约336.3万吨&#x…

完全二叉树你需要了解一下

完全二叉树介绍完全二叉树应用场景完全二叉树和满二叉树的区别完全二叉树代码示例拓展 完全二叉树介绍 完全二叉树(Complete Binary Tree)是一种特殊的二叉树,它的定义是:如果设二叉树的深度为h,除第h层外&#xff0c…

基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码

基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于白冠鸡优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要:针对PNN神经网络…

改进YOLOv8:结合Biformer——基于动态稀疏注意力构建高效金字塔网络架构

🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv8:我的专业笔记与技术总结   -- YOLOv8轻松上手, 适用技术小白,文章代码齐全,仅需 …

为什么AirtestIDE的selenium Window突然无法检索控件了?

1. 前言 最近有很多朋友跟我们反馈,为什么1.2.15版本的IDE没办法做网页元素检索了,是不是我们不支持selenium了之类的。 测试后发现,目前版本确实存在这个问题,原因是Chrome113.0.5672.127(最新)版本过高,AirtestIDE…

C语言--输入三角形的三边,输出三角形的面积

一.题目描述 输入三角形的三边,输出三角形的面积。比如:输入三角形的三边长度是3,4,5.输出6 二.思路分析 利用海伦公式可以很好解决 海伦公式的表达式如下: s (a b c) / 2 面积 sqrt((s * (s - a) * (s - b) * (…

基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度matlab程序

微❤关注“电气仔推送”获得资料(专享优惠) 参考文献: 基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度——陈登勇 主要内容: 以碳交易和碳封存成本、燃煤机组启停和煤耗成本、弃风成本、购气成本之和为目标函数&…

安装gitlab

安装gitlab 环境 关闭防火墙以及selinux,起码4核8G 内存至少 3G 不然启动不了 下载环境 gitlab官网:GitLab下载安装_GitLab最新中文基础版下载安装-极狐GitLab rpm包下载地址: [Yum - Nexus Repository Manager (gitlab.cn)](https://pack…

使用 ClickHouse 做日志分析

原作:Monika Singh & Pradeep Chhetri 这是我们在 Monitorama 2022 上发表的演讲的改编稿。您可以在此处找到包含演讲者笔记的幻灯片和此处的视频。 当 Cloudflare 的请求抛出错误时,信息会记录在我们的 requests_error 管道中。错误日志用于帮助解…

【Spring Boot】如何运用Spring Cache并设置缓存失效时间

简单描述 Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。CacheMan…

Arduino驱动Si7021温湿度传感器(温湿度传感器)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 Si7021温湿度传感器,应用了专用的数字模块采集技术和温湿度传感技术,具有极高的可靠性与卓越的长期稳定性。同时其体积小巧、精度高,特别是拥有毫秒级测试转换时间(DHT系列需要约2s的转换时间),启动测量与读…

【LeetCode刷题】--12.整数转罗马数字

12.整数转罗马数字 方法:模拟 分析罗马数字的规则是:对于罗马数字从左到右的每一位,选择尽可能大的符号值 根据罗马数字的唯一表示法,为了表示一个给定的整数num,寻找不超过num的最大符号值,将num减去该符…

UEC++ day7

敌人NPC机制 敌人机制分析与需求 新建一个character类来作为敌人,直接建蓝图设置骨骼网格,因为敌人可能多种就不规定死,然后这个敌人肯定需要两个触发器,一个用于大范围巡逻,一个用于是否达到主角近点进行攻击 注意我…

thinkphp8 DB_PREFIX 属性

设计表的时候使用**_user, **就是前缀,DB_PREFIX就是默认把前缀给去掉 在config/database.php prefix,改成你的前缀,数据库的表重命名‘ltf_user’ 代码调用 $user Db::name("user")->select();return json($user);之前是使用…

java springboot在测试类中构建虚拟MVC环境并发送请求

好 上文java springboot在测试类中启动一个web环境我们在测试类中搭了一个web环境 那么 下面就要想办法弄一个接口的测试 这边 我们还是要在controller包下去创建一个 controller类 写一个访问接口 这里 我创建一个 TestWeb.java 这里 我们编写代码如下 package com.example.…

九韵和声 饕餮盛宴丨音乐和声与校友情谊的完美交融

“九韻和聲”音樂會於11月19日晚上在深圳大劇院盛大舉行。來自各高校深圳校友會的逾千名同學們歡聚一堂,共同慶祝自己的合唱音樂會。 首次舉辦合唱音樂會 “九韵和声”音乐会由深圳市西安交通大学校友会牵头发起、主办,与深圳市清华大学校友会、深圳市浙…