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

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

☁️前言🎉🎉🎉

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

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

开始

推荐阅读

  • 设计模式导学:🚪传送门
  • 每日一个设计模式系列专栏:🚪传送门
  • 设计模式专属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);
    }
    
  • 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+"可口可乐");
        }
    }
    

    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();
    }
    
  • 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);
        }
    }
    

    2)MediumGlass:

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

    3)BigGlass:

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

  • Step5:编写配置文件

    glass-config.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <config>
        <!--用于反射获取Drink对象-->
        <drinkType>com.hhxy.drink.imp.CocaCola</drinkType>
        <drinkType>com.hhxy.drink.imp.LemonJuice</drinkType>
        <drinkType>com.hhxy.drink.imp.OrangeJuice</drinkType>
        <drinkType>com.hhxy.drink.imp.Sprite</drinkType>
        <drinkType>test</drinkType>
        <!--用于反射获取Glass对象-->
        <glassType>com.hhxy.glass.ext.SmallGlass</glassType>
        <glassType>com.hhxy.glass.ext.MediumGlass</glassType>
        <glassType>com.hhxy.glass.ext.BigGlass</glassType>
        <glassType>test</glassType>
    </config>
    
  • 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类!");
            }
    
        }
    }
    
  • 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();
    
        }
    }
    

    测试结果:

    image-20221008162945057

🌲总结

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

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

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

配合

思考


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

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

参考文章

在次致谢

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识汲取者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值