GoF计划模式——桥接模式

[复制链接]
发表于 昨天 20:48 | 显示全部楼层 |阅读模式
本文是【GoF计划模式】系列第10篇,更多内容欢迎关注公众号:咖啡八杯

媒介

为什么必要桥接模式?

假设要做一个图形编辑器,有圆形、矩形、三角形三种图形,每种图形又要支持赤色、蓝色、黄色三种颜色。用继续来实现,就要写 RedCircle、BlueCircle、RedRectangle、BlueRectangle... 类的数量 = 图形数 × 颜色数,3 × 3 = 9 个类。再加一种图形或一种颜色,类就会爆炸式增长。
  1. // 继承方案:类爆炸
  2. class RedCircle extends Circle { ... }
  3. class BlueCircle extends Circle { ... }
  4. class RedRectangle extends Rectangle { ... }
  5. // ... 9 个类,再加一个图形或颜色就要翻倍
复制代码
两种选择:要么忍受类爆炸,要么把颜色作为字段写在图形类里——后者看似简朴,但每种颜色逻辑都要在图形类内部判断,代码会变得痴肥,加一种颜色就要改全部图形类。
这种"两个维度各自厘革,组合后类爆炸"的抵牾,就是桥接模式要办理的题目。
概念

桥接模式(Bridge Pattern)是一种布局型计划模式,焦颔首脑是将抽象与实现分离到两个独立的维度,使它们可以各自独立扩展
桥接模式包罗四个脚色:

  • Abstraction(抽象):界说抽象部分的接口,持有对实现层的引用(这就是"桥")。通常用抽象类,由于必要持有引用并提供默认逻辑。
  • RefinedAbstraction(修正抽象):对抽象接口举行扩展,是抽象的具体变体。
  • Implementor(实现):界说实现部分的接口,与抽象层的接口可以完全差别。
  • ConcreteImplementor(具体实现):实现实现化接口的具体类。
classDiagram    direction BT    class Abstraction {                -impl: Implementor        +operation()    }    class RefinedAbstraction {        +operation()    }    class Implementor {                +operationImpl()    }    class ConcreteImplementorA {        +operationImpl()    }    class ConcreteImplementorB {        +operationImpl()    }    RefinedAbstraction --|> Abstraction : 继续    Abstraction o--> Implementor : 持有(桥)    ConcreteImplementorA ..|> Implementor : 实现    ConcreteImplementorB ..|> Implementor : 实现Abstraction 内部持有 Implementor 的引用,这是"桥"的关键——通过组合而非继续毗连两个维度。RefinedAbstraction 扩展抽象层,ConcreteImplementorA/B 实现实现层,双方各自独立发展。
可以把桥接模式明白为遥控器和电视的关系:遥控器是抽象层,电视是实现层。遥控器内部持有一个电视的引用,按下"开机"键时调用电视的开机方法。差别的遥控器(根本遥控器、万能遥控器)是修正抽象,差别品牌的电视(小米、索尼)是具体实现。如许遥控器和电视可以各自独立发展——新增一个电视品牌不必要改遥控器的代码,新增一种遥控器也不必要改电视的代码。
实现

桥接模式只有一种实现方式:抽象层持有实现层的引用,通过组合而非继续来关联两个维度。
根本实现

实现步调:界说实现层接口,创建具体实现类;界说抽象层基类(抽象类),内部持有实现层引用;创建修正抽象类扩展抽象层。
  1. // ===== 实现层接口 =====
  2. interface Implementor {
  3.     public void operationImpl();
  4. }
  5. // ===== 具体实现A =====
  6. class ConcreteImplementorA implements Implementor {
  7.     public void operationImpl() {
  8.         System.out.println("实现方式A");
  9.     }
  10. }
  11. // ===== 具体实现B =====
  12. class ConcreteImplementorB implements Implementor {
  13.     public void operationImpl() {
  14.         System.out.println("实现方式B");
  15.     }
  16. }
  17. // ===== 抽象层基类(用抽象类,因为需要持有实现层引用) =====
  18. abstract class Abstraction {
  19.     protected Implementor impl;  // 桥:持有实现层的引用
  20.     public Abstraction(Implementor impl) {
  21.         this.impl = impl;
  22.     }
  23.     public void operation() {
  24.         impl.operationImpl();  // 委托给实现层
  25.     }
  26. }
  27. // ===== 修正抽象 =====
  28. class RefinedAbstraction extends Abstraction {
  29.     public RefinedAbstraction(Implementor impl) {
  30.         super(impl);
  31.     }
  32.     public void operation() {
  33.         // 可以在委托前后加自己的逻辑
  34.         System.out.println("RefinedAbstraction 额外逻辑");
  35.         super.operation();
  36.     }
  37. }
  38. // ===== 客户端 =====
  39. public class Client {
  40.     public static void main(String[] args) {
  41.         Implementor implA = new ConcreteImplementorA();
  42.         Abstraction abs = new RefinedAbstraction(implA);
  43.         abs.operation();
  44.     }
  45. }
复制代码
引入一个例子:「一台万能遥控器,手里有小米电视和索尼电视两台装备。遥控器内部有个"电视插槽",插哪台电视就能控制哪台——遥控器本身不消改代码,电视也不消改接口」。
遥控器对应 Abstraction(抽象层),电视对应 Implementor(实现层),"插槽"对应构造方法注入的组合关系。换一台电视只需把新实例传给遥控器,双方各自独立。
  1. // 实现层:电视品牌
  2. interface TV {
  3.     public void powerOn();
  4.     public void powerOff();
  5. }
  6. class XiaomiTV implements TV {
  7.     public void powerOn() {
  8.         System.out.println("小米电视开机");
  9.     }
  10.     public void powerOff() {
  11.         System.out.println("小米电视关机");
  12.     }
  13. }
  14. class SonyTV implements TV {
  15.     public void powerOn() {
  16.         System.out.println("索尼电视开机");
  17.     }
  18.     public void powerOff() {
  19.         System.out.println("索尼电视关机");
  20.     }
  21. }
  22. // 抽象层:遥控器
  23. abstract class RemoteControl {
  24.     protected TV tv;  // 持有电视的引用(桥)
  25.     public RemoteControl(TV tv) {
  26.         this.tv = tv;
  27.     }
  28.     public void turnOn() {
  29.         tv.powerOn();
  30.     }
  31.     public void turnOff() {
  32.         tv.powerOff();
  33.     }
  34. }
  35. // 修正抽象:万能遥控器(额外功能)
  36. class UniversalRemote extends RemoteControl {
  37.     public UniversalRemote(TV tv) {
  38.         super(tv);
  39.     }
  40.     public void mute() {
  41.         System.out.println("静音");
  42.     }
  43. }
  44. // 使用:遥控器和电视自由组合
  45. RemoteControl basic = new RemoteControl(new XiaomiTV());
  46. basic.turnOn();  // 控制小米电视
  47. UniversalRemote universal = new UniversalRemote(new SonyTV());
  48. universal.turnOn();
  49. universal.mute();  // 万能遥控器额外功能
复制代码
为什么 Abstraction 用抽象类而不是接口? 桥接模式的抽象层必要:

  • 持有实现层的引用:接口不能有实例字段,只有抽象类才气生存 Implementor 实例
  • 提供默认的委托逻辑:operation() 方法通常直接委托给 impl,写在抽象类中制止子类重复代码
简朴说:接口界说"能做什么",抽象类界说"怎么做框架"。桥接模式的抽象层必要既有状态(持有引用)又有举动(默认委托),以是用抽象类更符合。
桥接模式与简朴组合的区别

桥接模式本质上就是组合,但它和寻常的"一个类持有另一个类"有本质区别:
维度简朴组合桥接模式目标复勤奋能、委托调用分离两个独立厘革的维度布局无束缚,随意组合抽象层 + 实现层 + 桥(持有引用)扩展性通常只有一方可扩展双方各自独立扩展举个例子:
  1. // 简单组合:订单持有支付方式的引用
  2. class Order {
  3.     private PaymentMethod payment;  // 组合:只是委托
  4.    
  5.     public void checkout() {
  6.         payment.pay(amount);
  7.     }
  8. }
复制代码
订单和付出方式之间没有"两个维度各自厘革"的关系——订单只有一个维度,付出方式也只有一个维度。目标只是"委托付出",不是办理类爆炸。
而桥接模式的图形 + 渲染:
  1. // 桥接:图形持有渲染器的引用
  2. abstract class Shape {
  3.     protected Renderer renderer;  // 桥:维度分离
  4.    
  5.     public void draw() {
  6.         renderer.render(this);
  7.     }
  8. }
复制代码
图形是一个维度(圆形、矩形...),渲染是另一个维度(矢量、像素...),双方各自独立扩展——新增一种图形不影响渲染层,新增一种渲染不影响图形层。
判断标准:新增一个实现类,抽象层是否必要新增对应的子类?

  • 简朴组合:新增 ApplePay,订单类不动——一方扩展,另一方不动
  • 桥接模式:新增渲染方式,图形类不动;新增图形,渲染类不动——双方都可以独立扩展
影象口诀组合是本事,桥接是布局。组合办理"复用",桥接办理"维度分离"。
总结

桥接模式本质上是用组合更换继续来办理"两个维度各自厘革"的题目——把乘法关系的类数量酿成加法关系。
什么时间用

  • 一个类有两个独立厘革的维度(如图形和颜色、付出方式和优惠计谋)
  • 每个维度都大概独立扩展,不渴望类数量爆炸
  • 必要在运行时动态切换实现层(如换一个电视品牌)
什么时间不消

  • 只有一个厘革维度,用继续或简朴组合就够了
  • 两个维度耦合精密,分开反而增长明白资本
  • 体系简朴,过早利用桥接模式会增长复杂度
简朴影象
桥接拆维度,组合替继续。两个方向各自变,类数从乘变加法。
模式接口关系核心意图范例场景桥接抽象持有实现引用分离两个独立厘革的维度图形+渲染、消息+渠道适配器目标接口 ≠ 被包装对象接口转换接口,让不兼容的类协同第三方库集成计谋上下文持有计谋引用更换同一维度的差别算法排序算法、扣头盘算抽象工厂工厂接口界说创建方法创建一系列相干对象跨数据库 DAO口诀对比:桥接拆维度,适配改接口,计谋换算法,工厂管创建。
桥接 vs 适配器

维度桥接模式适配器模式核心意图事前计划,分离两个独立厘革的维度事后调停,让不兼容的接口协同工作布局差异抽象层持有实现层引用,双方接口可差别适配器实现目标接口,持有被适配对象关注点防备类爆炸,支持独立扩展办理接口不兼容题目范例场景JDBC 驱动、跨平台 UI第三方库集成、遗留体系对接徐徐区分法

  • 假如两个维度都大概独立扩展 → 选桥接
  • 假如只是要让现有类协同工作 → 选适配器
  • 假如必要事后调停接口不兼容 → 选适配器
桥接 vs 计谋

维度桥接模式计谋模式核心意图分离两个独立厘革的维度更换同一维度的差别算法布局差异抽象层持有实现层引用,两者是平行的维度上下文持有计谋引用,计谋是算法的变体关注点维度分离,独立扩展算法更换,举动可变范例场景图形+渲染、消息+渠道排序算法、扣头盘算徐徐区分法

  • 假如有两个独立厘革的维度 → 选桥接
  • 假如只是同一个举动的差别实现方式 → 选计谋
  • 假如关注点是"用什么算法" → 选计谋
  • 假如关注点是"用什么实现体系" → 选桥接
桥接 vs 抽象工厂

维度桥接模式抽象工厂模式核心意图分离两个独立厘革的维度创建一系列相干对象布局差异抽象层持有实现层引用工厂接口界说创建方法关注点布局分离,运行时切换对象创建,产物族束缚范例场景JDBC 驱动、跨平台 UI跨数据库 DAO、跨 UI 主题徐徐区分法

  • 假如必要在运行时切换实现 → 选桥接
  • 假如只必要创建一系列相干对象 → 选抽象工厂
  • 假如关注点是"对象怎么创建" → 选抽象工厂
  • 假如关注点是"实现怎么切换" → 选桥接
训练标题

图形渲染体系

标题形貌:一个图形体系必要支持多种图形和多种渲染方式。图形包罗圆形(Circle)和矩形(Rectangle),渲染方式包罗矢量渲染(Vector)和像素渲染(Pixel)。请利用桥接模式实现,使得图形范例和渲染方式可以独立扩展。
输入形貌:第一行是一个整数 N(1 ≤ N ≤ 100),表现背面有 N 行输入。接下来的 N 行,每行包罗两个字符串,第一个表现图形范例(Circle / Rectangle),第二个表现渲染方式(Vector / Pixel)。
输出形貌:对于每行输入,输出该图形利用该渲染方式的绘制效果。
输入示例
  1. 6
  2. Circle Vector
  3. Circle Pixel
  4. Rectangle Vector
  5. Rectangle Pixel
  6. Circle Vector
  7. Rectangle Pixel
复制代码
输出示例
  1. Drawing Circle with Vector Renderer
  2. Drawing Circle with Pixel Renderer
  3. Drawing Rectangle with Vector Renderer
  4. Drawing Rectangle with Pixel Renderer
  5. Drawing Circle with Vector Renderer
  6. Drawing Rectangle with Pixel Renderer
复制代码
解题思绪:假如不利用桥接模式,就要为每种组合创建类(VectorCircle、PixelCircle、VectorRectangle、PixelRectangle),类数量 = 图形数 × 渲染数。利用桥接模式,渲染方式是实现层(Renderer 接口),图形是抽象层(Shape 抽象类持有 Renderer 引用),通过构造方法注入。如许图形和渲染各自独立扩展,类数量从乘法酿成加法。
  1. import java.util.*;
  2. public class Main {
  3.     public static void main(String[] args) {
  4.         Scanner sc = new Scanner(System.in);
  5.         int n = sc.nextInt();
  6.         while (n-- > 0) {
  7.             String shapeType = sc.next();
  8.             String rendererType = sc.next();
  9.             // 实现层:渲染方式
  10.             Renderer r = null;
  11.             if ("Vector".equals(rendererType)) {
  12.                 r = new Vector();
  13.             } else {
  14.                 r = new Pixel();
  15.             }
  16.             // 抽象层:图形(持有渲染器引用)
  17.             Shape s = null;
  18.             if ("Circle".equals(shapeType)) {
  19.                 s = new Circle(r);
  20.             } else {
  21.                 s = new Rectangle(r);
  22.             }
  23.             s.draw();
  24.         }
  25.     }
  26. }
  27. // 实现层接口:渲染器
  28. interface Renderer {
  29.     public String getName();
  30. }
  31. class Vector implements Renderer {
  32.     public String getName() {
  33.         return "Vector Renderer";
  34.     }
  35. }
  36. class Pixel implements Renderer {
  37.     public String getName() {
  38.         return "Pixel Renderer";
  39.     }
  40. }
  41. // 抽象层:图形(持有渲染器引用)
  42. abstract class Shape {
  43.     protected Renderer renderer;
  44.     protected String name;
  45.     public Shape(String name, Renderer renderer) {
  46.         this.name = name;
  47.         this.renderer = renderer;
  48.     }
  49.     public void draw() {
  50.         System.out.println("Drawing " + name + " with " + renderer.getName());
  51.     }
  52. }
  53. class Circle extends Shape {
  54.     public Circle(Renderer r) {
  55.         super("Circle", r);
  56.     }
  57. }
  58. class Rectangle extends Shape {
  59.     public Rectangle(Renderer r) {
  60.         super("Rectangle", r);
  61.     }
  62. }
复制代码
扩展:现实项目中的桥接模式

JDBC 驱动架构

JDBC 是桥接模式最经典的应用。Connection、Statement 等接口是抽象层,各数据库厂商的驱动(MySQL Connector、PostgreSQL Driver)是实现层。应用代码只依靠 JDBC 接口,不关心底层是哪个数据库——MySQL 升级驱动版本不影响业务代码,切换数据库只需换驱动和毗连字符串。
消息关照体系

关照体系必要支持多种消息范例(寻常、告急、营销)和多种发送渠道(短信、邮件、微信)。用桥接模式,消息范例是抽象层,发送渠道是实现层。新增一种消息范例只需加一个 Message 子类,新增一种发送渠道只需加一个 MessageSender 实现,两者互不影响。
日记框架的 Appender

SLF4J + Logback 中,Logger 是抽象层,Appender(输出目标)是实现层。一个 Logger 可以设置多个 Appender,日记同时输出到控制台、文件、远程服务。新增异步 Logger 不影响 Appender,新增 Kafka Appender 不影响 Logger。
跨平台 UI 组件

UI 框架中,按钮是一个维度,利用体系是另一个维度。同一种按钮在差别利用体系上的渲染方式差别。用桥接模式,UI 组件是抽象层,利用体系渲染是实现层。新增下拉框不影响利用体系层,新增 Linux 支持不影响 UI 组件层。
付出方式与优惠计谋

电商体系中,付出方式(付出宝、微信)是一个维度,优惠计谋(无优惠、满减、扣头)是另一个维度。用桥接模式,订单是抽象层,付出方式是实现层。新增 Apple Pay 不影响优惠计谋,新增"首单立减"不影响付出方式。
现在大概还在写简朴的业务代码,但比及遇到"两个维度都要扩展"的场景时——与其让类数量爆炸,不如用桥接模式把它们拆开,各自独立发展。其时间就真的懂了。
技能互换 & 更多原创内容,关注公众号:咖啡八杯

免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表