【设计模式——学习笔记】23种设计模式——桥接模式Bridge(原理讲解+应用场景介绍+案例介绍+Java代码实现)

简介: 【设计模式——学习笔记】23种设计模式——桥接模式Bridge(原理讲解+应用场景介绍+案例介绍+Java代码实现)

问题引入

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图

【对应类图】

【分析】

  • 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
  • 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
  • 解决方案:使用桥接模式

介绍

基础介绍


  • 桥接模式(Bridge模式)是指: 将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
  • Bridge 的意思是“桥梁”。就像在现实世界中,桥梁的功能是将河流的两侧连接起来一样Bridge 模式的作用也是将两样东西连接起来,它们分别是类的功能层次结构和类的实现层次结构。
  • 是一种结构型设计模式
  • Bridge模式基于类的最小设计原则(实现功能的同时,让类尽可能少),通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(lmplementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展

类的功能层次结构

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

父类本身具备一些基本功能,子类在继承父类的功能之外,还可以添加新的功能。这种层次结构被称为“类的功能层次结构”。如果需需要在SomethingGood的基础上继续增加新的功能,再写SomethingGood的子类即可。

注意:类的层次结构关系不应当过深

类的实现层次结构

抽象类声明了一些抽象方法,定义了接口(API),然后子类负责去实现这些抽象方法。父类的任务是通过声明抽象方法的方式定义接口(API),而子类的任务是实现抽象方法。正是由于父类和子类的这种任务分担,我们才可以编写出具有高可替换性的类。


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

这种类的层次结构(类的实现层次结构)并非用于增加功能,并不方便我们增加新的方法。它的真正作用是帮助我们实现下面这样的任务分担

  • 类通过声明抽象方法来定义接口(API)
  • 子类通过实现具体方法来实现接口(API)

一个抽象类可以有多种子实现类

层次结构分离

当类的层次结构只有一层时,功能层次结构与实现层次结构是混杂在一个层次结构中的。这样很容易使类的层次结构变得复杂,也难以透彻地理解类的层次结构。因为自己难以确定究竟应该在类的哪一个层次结构中去增加子类。


因此,我们需要将“类的功能层次结构”与“类的实现层次结构”分离为两个独立的类层次结构。当然,如果只是简单地将它们分开,两者之间必然会缺少联系。所以我们还需要在它们之间搭建一座桥梁,这就是桥接模式。

类图

抽象类和接口是聚合关系,也是调用和被调用关系

应用场景

对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用,如以下场景:

  • JDBC驱动程序
  • 银行转账系统
  • 转账分类: 网上转账,柜台转账,AMT转账
  • 转账用户类型: 普通用户,银卡用户,金卡用户
  • 消息管理
  • 消息类型: 即时消息,延时消息
  • 息分类: 手机短信,邮件消息,QQ消息…

案例

案例一

类图

代码实现

【接口】

package com.atguigu.bridge;
/**
 * 接口
 */
public interface Brand {
   void open();
   void close();
   void call();
}

【接口实现类:小米手机】

package com.atguigu.bridge;
public class XiaoMi implements Brand {
   @Override
   public void open() {
      System.out.println(" 小米手机开机 ");
   }
   @Override
   public void close() {
      System.out.println(" 小米手机关机 ");
   }
   @Override
   public void call() {
      System.out.println(" 小米手机打电话 ");
   }
}

【接口实现类:Vivo手机】

package com.atguigu.bridge;
public class Vivo implements Brand {
   @Override
   public void open() {
      System.out.println(" Vivo手机开机 ");
   }
   @Override
   public void close() {
      System.out.println(" Vivo手机关机 ");
   }
   @Override
   public void call() {
      System.out.println(" Vivo手机打电话 ");
   }
}

【抽象类】

package com.atguigu.bridge;
public abstract class Phone {
   /**
    * 聚合品牌
    */
   private Brand brand;
   /**
    * 构造器
    * @param brand
    */
   public Phone(Brand brand) {
      super();
      this.brand = brand;
   }
   protected void open() {
      this.brand.open();
   }
   protected void close() {
      this.brand.close();
   }
   protected void call() {
      this.brand.call();
   }
}

【抽象类子类:折叠手机】

package com.atguigu.bridge;
/**
 * 折叠式手机类,继承 抽象类 Phone
 */
public class FoldPhone extends Phone {
   //构造器
   public FoldPhone(Brand brand) {
      super(brand);
   }
   public void open() {
      System.out.println(" 折叠样式手机 ");
      // 实际上调用的是具体品牌(如Xiaomi)的开机方法,抽象类Phone充当桥接作用
      super.open();
   }
   public void close() {
      System.out.println(" 折叠样式手机 ");
      super.close();
   }
   public void call() {
      System.out.println(" 折叠样式手机 ");
      super.call();
   }
}

【抽象类子类:直立手机】

package com.atguigu.bridge;
public class UpRightPhone extends Phone {
   //构造器
   public UpRightPhone(Brand brand) {
      super(brand);
   }
   public void open() {
      System.out.println(" 直立样式手机 ");
      super.open();
   }
   public void close() {
      System.out.println(" 直立样式手机 ");
      super.close();
   }
   public void call() {
      System.out.println(" 直立样式手机 ");
      super.call();
   }
}

【客户端】

package com.atguigu.bridge;
public class Client {
   public static void main(String[] args) {
      //获取折叠式手机 (样式 + 品牌 才是具体的手机)
      Phone phone1 = new FoldPhone(new XiaoMi());
      phone1.open();
      phone1.call();
      phone1.close();
      System.out.println("==============");
      Phone phone2 = new FoldPhone(new Vivo());
      phone2.open();
      phone2.call();
      phone2.close();
      System.out.println("==============");
      UpRightPhone phone3 = new UpRightPhone(new XiaoMi());
      phone3.open();
      phone3.call();
      phone3.close();
      System.out.println("==============");
      UpRightPhone phone4 = new UpRightPhone(new Vivo());
      phone4.open();
      phone4.call();
      phone4.close();
   }
}

【运行】

折叠样式手机 
 小米手机开机 
 折叠样式手机 
 小米手机打电话 
 折叠样式手机 
 小米手机关机 
==============
 折叠样式手机 
 Vivo手机开机 
 折叠样式手机 
 Vivo手机打电话 
 折叠样式手机 
 Vivo手机关机 
==============
 直立样式手机 
 小米手机开机 
 直立样式手机 
 小米手机打电话 
 直立样式手机 
 小米手机关机 
==============
 直立样式手机 
 Vivo手机开机 
 直立样式手机 
 Vivo手机打电话 
 直立样式手机 
 Vivo手机关机 
Process finished with exit code 0

分析

  • 无论增加一个样式或者增加一个新的手机品牌,都只需要增加一个类

案例二

类图

Display使用DispalyImpl的方法来完成功能,StringDisplayImpl负责DispalyImpl方法的具体实现,CountDisplay用来完成更多的功能

代码实现

【类的功能层次结构:Display】

package com.atguigu.bridge.Sample;
/**
 * 类的功能层次最上层
 */
public class Display {
    /**
     * 实现了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();
    }
}

private DisplayImpl impl;使用了“委托”关系,而不是使用“继承”关系。继承是强关联关系,委托是弱关联关系。使用委托更方便扩展。

【类的功能层次结构:CountDisplay】

package com.atguigu.bridge.Sample;
/**
 * 在Display类的基础上增加新功能
 */
public class CountDisplay extends Display {
    public CountDisplay(DisplayImpl impl) {
        super(impl);
    }
    /**
     * 循环显示times次
     *
     * @param times
     */
    public void multiDisplay(int times) {
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
}

【类的实现层次结构:上层】

package com.atguigu.bridge.Sample;
/**
 * 类的实现层次最上层
 */
public abstract class DisplayImpl {
    public abstract void rawOpen();
    public abstract void rawPrint();
    public abstract void rawClose();
}

【类的实现层次结构:下层】

package com.atguigu.bridge.Sample;
/**
 * 类的实现层次结构,具体实现类
 */
public class StringDisplayImpl extends DisplayImpl {
    /**
     * 要显示的字符串
     */
    private String string;
    /**
     * 以字节单位计算出的字符串的宽度
     */
    private int width;
    /**
     * 构造方法
     *
     * @param string 构造函数接收要显示的字符串string
     */
    public StringDisplayImpl(String string) {
        // 将它保存在字段中
        this.string = string;
        // 把字符串的宽度也保存在字段中,以供使用
        this.width = string.getBytes().length;
    }
    public void rawOpen() {
        printLine();
    }
    public void rawPrint() {
        // 前后加上"|"并显示
        System.out.println("|" + string + "|");
    }
    public void rawClose() {
        printLine();
    }
    private void printLine() {
        // 显示用来表示方框的角的"+"
        System.out.print("+");
        // 显示width个"-"
        for (int i = 0; i < width; i++) {
            // 将其用作方框的边框
            System.out.print("-");                    
        }
        // 显示用来表示方框的角的"+"
        System.out.println("+");
    }
}

【主类】

package com.atguigu.bridge.Sample;
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();
        System.out.println();
        d2.display();
        System.out.println();
        d3.display();
        d3.multiDisplay(5);
    }
}

【运行】

+-------------+
|Hello, China.|
+-------------+
+-------------+
|Hello, World.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+
Process finished with exit code 0

拓展一

在上述示例程序中增加一个类,实现“显示字符串若干(随机)次”的功能。

【新增类】

package com.atguigu.bridge.A1;
import java.util.Random;
public class RandomCountDisplay extends CountDisplay {
    private Random random = new Random();
    public RandomCountDisplay(DisplayImpl impl) {
        super(impl);
    }
    public void randomDisplay(int times) {
        multiDisplay(random.nextInt(times));
    }
}

【主类】

package com.atguigu.bridge.A1;
public class Main {
    public static void main(String[] args) {
        RandomCountDisplay d = new RandomCountDisplay(new StringDisplayImpl("Hello, China."));
        d.randomDisplay(10);
    }
}

扩展二

在上述示例程序中增加一个类,实现“显示文本文件的内容”的功能。

【新增类】

package com.atguigu.bridge.A2;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FileDisplayImpl extends DisplayImpl {
    private String filename;
    private BufferedReader reader;
    /**
     * 循环显示的极限(缓存大小限制)
     */
    private final int MAX_READAHEAD_LIMIT = 4096;  
    public FileDisplayImpl(String filename) {
        this.filename = filename;
    }
    public void rawOpen() {
        try {
            reader = new BufferedReader(new FileReader(filename));
            reader.mark(MAX_READAHEAD_LIMIT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 装饰框
        System.out.println("=-=-=-=-=-= " + filename + " =-=-=-=-=-="); 
    }
    public void rawPrint() {
        try {
            String line;
            reader.reset(); // 回到mark的位置
            while ((line = reader.readLine()) != null) {
                System.out.println("> " + line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void rawClose() {
        // 装饰框
        System.out.println("=-=-=-=-=-= "); 
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

【主类】

package com.atguigu.bridge.A2;
public class Main {
    public static void main(String[] args) {
        CountDisplay d = new CountDisplay(new FileDisplayImpl("star.txt"));
        d.multiDisplay(3);
    }
}

登场角色

  • Abstraction(抽象化):位于类的功能层次结构的最上层。它使用Implementor角色的方法定义了基本的功能。该角色中保存了Implementor 角色的实例
  • RefinedAbstraction(改善后的抽象化):在Abstraction角色的基础上增加了新功能
  • Implementor(实现者):位于“类的实现层次结构”的最上层。它定义了用于实现Abstraction 角色的接口(API)的方法
  • Concretelmplementor( 具体实现者):负责实现在Implementor 角色中定义的接口(API)

桥接模式在JDBC源码中的应用

总结

  • 实现了抽象和实现部分的分离,从而极大的提高了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统
  • 分离之后代码的扩展性更强,当想要增加功能时,只需要在“类的功能层次结构”一侧增加类即可,不必对“类的实现层次结构”做任何修改。而且,增加后的功能可以被“所有的实现”使用(例如,我们可以将“类的功能层次结构”应用于软件所运行的操作系统上。如果我们将某个程序中依赖于操作系统的部分划分为 Windows版、Macintosh 版、Unix 版,那么我们就可以用 Bridge模式中的“类的实现层次结构”来表现这些依赖于操作系统的部分。也就是说,我们需要编写一个定义这些操作系统的共同接口(API)的Implementor角色,然后编写Windows版、Macintosh版Unix版的3个Concretelmplementor角色。这样一来,无论在“类的功能层次结构”中增加多少个功能,它们都可以工作于这3个操作系统上。)
  • 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
  • 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
  • 桥接模式的引入增加了系统的理解和设计难度(较难分析出哪些是抽象层,哪些是实现层),由于聚合关联关系建立在抽象层要求开发者针对抽象进行设计和编程
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面
目录
相关文章
|
1月前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
74 5
|
2天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
3天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
17 3
|
3天前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
22 2
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
1月前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
1月前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
1月前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
67 2
|
1月前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
49 5
|
1月前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
60 1

热门文章

最新文章