• 每日一个设计模式之【桥接模式】


    每日一个设计模式之【桥接模式

    ☁️前言🎉🎉🎉

      大家好✋,我是知识汲取者😄,今天给大家带来一篇有关桥接模式的学习笔记。众所周知能够熟练使用设计模式是一个优秀程序猿的必备技能,当我们在项目中选择一个或多个合适的设计模式,不仅能大大提高项目的稳健性可移植性可维护性,同时还能让你的代码更加精炼,具备艺术美感

      桥接模式是一个很神器的模式,它能够大幅度降低系统类的数量,系统越复杂、效果越明显,它基于类的最小设计原则,通过使用封装、聚合、继承等行为让不同的类承担不同的职责,现在就让我们一起学习吧(●’◡’●)

    开始

    推荐阅读

    • 设计模式导学:🚪传送门
    • 每日一个设计模式系列专栏:🚪传送门
    • 设计模式专属Gitee仓库:✈启程
    • 设计模式专属Github仓库:🚀上路
    • 知识汲取者的个人主页:💓点我哦

    🌻桥接模式概述

    • 什么是桥接模式

      桥接模式(Bridge Pattern)又称为柄体(Handle and Body)模式或接口(Interface)模式,它是一种结构型模式,将实现与抽象放在两个不同的层次中,使两个层次可以独立改变

    • 桥接模式的作用:将抽象部分与它的实现部分分离开来,使他们都可以独立变化

    • 桥接模式的优缺点

      • 优点

        • 实现了抽象层和实现层的解耦。解耦能够提高系统的维护性、扩展性、符合开闭原则
        • 提高系统的透明度。桥接模式是面对抽象层开发的,用户只需要关注接口,不需要关注接口的具体实现
        • 降低了系统的复杂度。使用桥接模式可以取代多层继承方案,极大地减少了子类的个数

        ……

      • 缺点

        • 桥接模式要求正确识别出系统中两个独立变化的维度,较为困难

        ……

    • 桥接模式的适用场景

      • 需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系
      • 不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统
      • 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立地进行扩展

      ……

      生活中的应用:笔和笔芯,一只一样的笔只要搭配不同的笔芯就能得到不一样的笔;开关,它将抽象和实现进行了分离,用户只需要关注开关这个抽象的东西,就能实现某种功能;

      Java中的应用:在JDBC的驱动管理就使用了桥接模式,不同的数据库驱动名称放到Class.forName()中就能获取到对应的数据库连接

    • 桥接模式的角色划分

      • 抽象化角色(Abstraction):定义了类的行为,内部还保存了一个实现化角色的引用,是桥接模式的核心,一般是一个抽象类
      • 扩展抽象化角色(RefinedAbstraction):是抽象化角色的子类,用于确定类的行为(行为的具体实现细节需要通过调用实现化角色的引用),是与客户进行交互的类
      • 实现化角色(Implementor):定义类的行为,但不实现
      • 具体实现化角色(ConcreteImplementor):是实现化角色的具体实现,包含了行为实现的细节

    🌱桥接模式的实现

    示例

    问题描述:一家小卖部它卖雪碧、可口可乐、橙汁、柠檬汁四款饮料,每种饮料都由大、中、小三中型号,请使用Java语言模拟实现饮料的售卖。

    思考:如果不使用桥接模式,则需要创建4*3个类,类数量的增长级别是n*m;如果使用桥接模式,只需要4+3个类,类数量的增长级别是n+m。显然这里十分适合使用桥接模式,我们从两个维度对客户买饮料这个行为进行分析,买什么种类的饮料?买什么型号的饮料?从而创建一个抽象化角色和实现化角色,由面对对象的思维:对象的行为由对象自己触发(例如:关门这个方法在门类中,通过门对象调用),所以买饮料的这个行为的具体实现可以放在Drink类中(确定了实现化角色),那么抽象化角色就应该是Glass类,分析完毕🤗……

    image-20221112223124390

    创建实现化角色
    创建具体实现化角色
    创建抽象化角色
    创建扩展抽象化角色
    编写配置文件
    编写配置文件读取类
    测试
    • Step1:创建实现化角色

      Drink:

      package com.hhxy.drink;
      
      /**
       * @author ghp
       * @date 2022/10/8
       * @title
       * @description
       */
      public interface Drink {
      
          /**
           * 喝饮料的具体实现方法(确定了喝那种种饮料?喝什么型号的饮料)
           * @param type
           */
          void drink(String type);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • Step2:创建具体实现化角色

      1)CocaCola:

      package com.hhxy.drink.imp;
      
      import com.hhxy.drink.Drink;
      
      /**
       * @author ghp
       * @date 2022/10/5
       * @title 可口可乐
       * @description
       */
      public class CocaCola implements Drink {
      
          @Override
          public void drink(String type) {
              System.out.println("我想喝"+type+"可口可乐");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      2)LemonJuice:

      具体请参考Github或Gitee仓库,略…

      3)OrangeJuice:

      具体请参考Github或Gitee仓库,略……

      4)Sprite

      具体请参考Github或Gitee仓库,略……

    • Step3:创建抽象化角色

      package com.hhxy.glass;
      
      import com.hhxy.drink.Drink;
      
      /**
       * @author ghp
       * @date 2022/10/8
       * @title
       * @description
       */
      public abstract class Glass {
      
          //使用protected让子类能够访问,但是不让外部其它包访问,进一步确保封装性
          protected Drink drink;
          //将子类的共同属性抽象出来,减少重复定义
          protected String type;
      
          public Glass(Drink drink){
              this.drink = drink;
          }
      
          /**
           * 喝饮料的方法
           */
          public abstract void drink();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
    • Step4:创建扩展抽象化角色

      1)SmallGlass:

      package com.hhxy.glass.ext;
      
      import com.hhxy.drink.Drink;
      import com.hhxy.glass.Glass;
      
      /**
       * @author ghp
       * @date 2022/10/5
       * @title 小杯
       * @description
       */
      public class SmallGlass extends Glass {
      
          public SmallGlass(Drink drink) {
              super(drink);
          }
      
          /**
           * 喝饮料的方法
           */
          @Override
          public void drink() {
              type = "小杯";
              drink.drink(type);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

      2)MediumGlass:

      具体请参考Github或Gitee仓库,略……

      3)BigGlass:

      具体请参考Github或Gitee仓库,略……

    • Step5:编写配置文件

      glass-config.xml:

      
      <config>
          
          <drinkType>com.hhxy.drink.imp.CocaColadrinkType>
          <drinkType>com.hhxy.drink.imp.LemonJuicedrinkType>
          <drinkType>com.hhxy.drink.imp.OrangeJuicedrinkType>
          <drinkType>com.hhxy.drink.imp.SpritedrinkType>
          <drinkType>testdrinkType>
          
          <glassType>com.hhxy.glass.ext.SmallGlassglassType>
          <glassType>com.hhxy.glass.ext.MediumGlassglassType>
          <glassType>com.hhxy.glass.ext.BigGlassglassType>
          <glassType>testglassType>
      config>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • Step6:编写配置文件读取类

      ReadGlassConfig:

      package com.hhxy.read;
      
      import com.hhxy.drink.Drink;
      import com.hhxy.glass.Glass;
      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.lang.reflect.Constructor;
      
      /**
       * @author ghp
       * @date 2022/10/5
       * @title 读取杯子的配置文件
       * @description
       */
      public class ReadGlassConfig {
      
          public static Glass getGlass(){
      
              try{
                  //1、将配置文件加载到内存中,获取DOM对象
                  //1.1 获取DOM解析器工厂对象DocumentBuilderFactory,用于创建DOM解析器
                  DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
                  //1.2 获取DOM解析器DocumentBuilder
                  DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
                  //1.3 加载配置文件
      //            Document document = documentBuilder.parse(new FileInputStream("day06_Bridge/src/glass-config.xml"));
                  //让代码和模块名进行解耦,比上面那种方法更优
                  Document document = documentBuilder.parse(ReadGlassConfig.class.getResourceAsStream("/glass-config.xml"));
      
                  //2、获取配置文件中的数据
                  //2.1 从DOM中获取指定的结点的结点列表
                  NodeList nodeListDrink = document.getElementsByTagName("drinkType");
                  NodeList nodeListGlass = document.getElementsByTagName("glassType");
                  //2.2 获取指定位置的结点
                  Node classNodeDrink = nodeListDrink.item(0).getFirstChild();
                  Node classNodeGlass = nodeListGlass.item(0).getFirstChild();
                  //2.3 获取指定结点中的数据(排除空格)
                  String drinkType = classNodeDrink.getNodeValue().trim();
                  String glassType = classNodeGlass.getNodeValue().trim();
      
                  //3、使用反射获取获取Glass对象
                  //3.1 获取类对象
                  Class clsDrink = Class.forName(drinkType);
                  Class clsGlass = Class.forName(glassType);
                  //3.2 获取该类对象的构造器对象
                  Constructor constructorDrink = clsDrink.getDeclaredConstructor();
                  Constructor constructorGlass = clsGlass.getDeclaredConstructor(Drink.class);
                  //3.3 暴力反射,防止构造器私有化导致无法创建对象
                  constructorDrink.setAccessible(true);
                  constructorGlass.setAccessible(true);
                  //3.4 获取Drink、Glass对象
                  Drink drink = (Drink) constructorDrink.newInstance();
                  Glass glass = (Glass) constructorGlass.newInstance(drink);
      
                  //4、返回通过配置文件获取的Glass对象
                  return glass;
              } catch (Exception e) {
                  //如果异常就打印异常信息,同时返回一个空
                  e.printStackTrace();
                  throw new RuntimeException("未找到该Glass类或Drink类,请检查配置文件或者添加一个Glass类或Drink类!");
              }
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
    • Step7:测试

      package com.hhxy.test;
      
      import com.hhxy.glass.Glass;
      import com.hhxy.read.ReadGlassConfig;
      
      /**
       * @author ghp
       * @date 2022/10/5
       * @title 测试类
       * @description 用于测试桥接模式
       */
      public class Test {
          public static void main(String[] args) {
              /*
              方式一:通过new获取Drink对象喝Glass对象
              Drink drink = new CocaCola();
              Glass glass = new BigGlass(drink);
              glass.drink();
              */
      
              //方式二:通过读取配置文件获取Drink对象喝Glass对象,实现了解耦,同时也很方便测试
              Glass glass = ReadGlassConfig.getGlass();
              glass.drink();
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

      测试结果:

      image-20221008162945057

    🌲总结

    又到了我最喜欢的总结时间了(●ˇ∀ˇ●),总的来讲:

    • 桥接模式是一种结构型模式,它将抽象和实现进行解耦,能很大程度降低系统中类的数量,提高系统的灵活性、可维护性、可扩展性
    • 桥接模式由于需要从类的两个维度,在实际应用中是有一定难度的
    • 其次就是要注意桥接模式的主要应用场景,当类中存在大量继承关系式,可以选择使用桥接模式;或者让系统的抽象与实现充分解耦,降低系统的透明度,也可以选择桥接模式

    自此,文章就结束了,如果觉得本文对你有一丢丢帮助的话😄,欢迎点赞👍+评论✍,您的支持将是我写出更加优秀文章的动力O(∩_∩)O

    配合

    思考


    上一篇:每日一个设计模式之【适配器模式】

    下一篇:每日一个设计模式之【组合模式】

    参考文章

    在次致谢

  • 相关阅读:
    宝塔下 php7.4 使用kafka
    AOT和单文件发布对程序性能的影响
    解决google chrome helper 内存占用较高!
    Element & 8080端口被占用
    SQL引擎子系统的工作原理
    如何使用C++图形界面开发框架Qt创建一个应用程序?(Part 2)
    1 make概述
    如何在LeetCode刷题时,遇到一个代码量更多的但是运行时间却更短?
    使用低代码可视化开发平台快速搭建应用
    JavaScript基础标准库总结——(1)
  • 原文地址:https://blog.csdn.net/qq_66345100/article/details/127826643