桥接模式

Metadata

title: 桥接模式
date: 2022-12-14 15:41
tags:
  - 行动阶段/完成
  - 主题场景/设计
  - 笔记空间/KnowladgeSpace/ProgramSpace/ProjectSpace
  - 细化主题/设计模式/结构模式/桥接模式
categories:
  - 设计
keywords:
  - 设计模式/结构模式/桥接模式
description: 桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。

桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。

简介

意图:
将抽象部分与实现部分分离,使它们都可以独立的变化。

主要解决:
在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。

何时使用:
实现系统可能有多个角度分类,每一种角度都可能变化。

如何解决:
把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。

关键代码:
抽象类依赖实现类。

应用实例:

  1. 猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。
  2. 墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。

优点:

  1. 抽象和实现的分离。
  2. 优秀的扩展能力。
  3. 实现细节对客户透明。

缺点:
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

使用场景:

  1. 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  2. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  3. 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。

注意事项:
对于两个独立变化的维度,使用桥接模式再适合不过了。

模式结构

  1. 抽象部分 (Abstraction) 提供高层控制逻辑, 依赖于完成底层实际工作的实现对象。
  2. 实现部分 (Implementation) 为所有具体实现声明通用接口。 抽象部分仅能通过在这里声明的方法与实现对象交互。
  3. 抽象部分可以列出和实现部分一样的方法, 但是抽象部分通常声明一些复杂行为, 这些行为依赖于多种由实现部分声明的原语操作。
  4. 具体实现 (Concrete Implementations) 中包括特定于平台的代码。
  5. 精确抽象 (Refined Abstraction) 提供控制逻辑的变体。 与其父类一样, 它们通过通用实现接口与不同的实现进行交互。
  6. 通常情况下, 客户端 (Client) 仅关心如何与抽象部分合作。 但是, 客户端需要将抽象对象与一个实现对象连接起来。

适合应用场景

title: 如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。

 类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。

桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 你可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。
title: 如果你希望在几个独立维度上扩展一个类, 可使用该模式。

桥接建议将每个维度抽取为独立的类层次。 初始类将相关工作委派给属于对应类层次的对象, 无需自己完成所有工作。
title:  如果你需要在运行时切换不同实现方法, 可使用桥接模式。

当然并不是说一定要实现这一点, 桥接模式可替换抽象部分中的实现对象, 具体操作就和给成员变量赋新值一样简单。

顺便提一句, 最后一点是很多人混淆桥接模式和策略模式的主要原因。 记住, 设计模式并不仅是一种对类进行组织的方式, 它还能用于沟通意图和解决问题。

实现方式

  1. 明确类中独立的维度。 独立的概念可能是: 抽象/平台, 域/基础设施, 前端/后端或接口/实现。
  2. 了解客户端的业务需求, 并在抽象基类中定义它们。
  3. 确定在所有平台上都可执行的业务。 并在通用实现接口中声明抽象部分所需的业务。
  4. 为你域内的所有平台创建实现类, 但需确保它们遵循实现部分的接口。
  5. 在抽象类中添加指向实现类型的引用成员变量。 抽象部分会将大部分工作委派给该成员变量所指向的实现对象。
  6. 如果你的高层逻辑有多个变体, 则可通过扩展抽象基类为每个变体创建一个精确抽象。
  7. 客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。 此后, 客户端只需与抽象对象进行交互, 无需和实现对象打交道。

示例

示例

collapse: closed

抽象化角色:

package com.charon.bridge;

/**

  • @className: Brand

  • @description: 抽象化产品角色

  • @author: charon

  • @create: 2022-03-18 22:51
    */
    public interface Brand {
    void open();

    void close();

    void call();

}

扩展抽象化角色:

package com.charon.bridge;

/**

  • @className: Vivo

  • @description:

  • @author: charon

  • @create: 2022-03-18 22:55
    */
    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.charon.bridge;

/**

  • @className: XiaoMi

  • @description:

  • @author: charon

  • @create: 2022-03-18 22:56
    */
    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("小米手机打电话。。。。");
    }

}

实现化角色:

package com.charon.bridge;

/**

  • @className: Phone

  • @description: 实现化角色

  • @author: charon

  • @create: 2022-03-18 22:52
    */
    public abstract class Phone {

    private Brand brand;

    public Phone(Brand brand) {
    this.brand = brand;
    }

    protected void open(){
    this.brand.open();
    }

    protected void call(){
    this.brand.call();
    }

    protected void close(){
    this.brand.close();
    }

}

具体实现化角色:

package com.charon.bridge;

/**

  • @className: FoldedPhone

  • @description:

  • @author: charon

  • @create: 2022-03-18 22:57
    */
    public class FoldedPhone extends Phone{
    public FoldedPhone(Brand brand) {
    super(brand);
    }

    @Override
    protected void open(){
    super.open();
    System.out.println("折叠样式手机开机。。。。");
    }

    @Override
    protected void call(){
    super.call();
    System.out.println("折叠样式手机打电话。。。。");
    }

    @Override
    protected void close(){
    super.close();
    System.out.println("折叠样式手机关机。。。。");
    }

}

package com.charon.bridge;

/**

  • @className: UpRightPhone

  • @description:

  • @author: charon

  • @create: 2022-03-18 22:59
    */
    public class UpRightPhone extends Phone{

    public UpRightPhone(Brand brand) {
    super(brand);
    }

    @Override
    protected void open(){
    super.open();
    System.out.println("直立样式手机开机。。。。");
    }

    @Override
    protected void call(){
    super.call();
    System.out.println("直立样式手机打电话。。。。");
    }

    @Override
    protected void close(){
    super.close();
    System.out.println("直立样式手机关机。。。。");
    }

}

测试:

package com.charon.bridge;

/**

  • @className: Client
  • @description:
  • @author: charon
  • @create: 2022-03-18 22:51
    */
    public class Client {
    public static void main(String[] args) {
    // 获取折叠样式手机(品牌+样式)
    FoldedPhone xiaomiFoldedPhone = new FoldedPhone(new XiaoMi());
    xiaomiFoldedPhone.open();
    xiaomiFoldedPhone.call();
    xiaomiFoldedPhone.close();

    UpRightPhone xiaomiUpRightPhone = new UpRightPhone(new XiaoMi());
    xiaomiUpRightPhone.open();
    xiaomiUpRightPhone.call();
    xiaomiUpRightPhone.close();
    }

}

优缺点

title: 优点

- 你可以创建与平台无关的类和程序。
- 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
- 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。
- 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。

缺点

  • 对高内聚的类使用该模式可能会让代码更加复杂。

与其他模式的关系

[[桥接模式]]通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, [[适配器模式]]通常在已有程序中使用, 让相互不兼容的类能很好地合作。

[[桥接模式]]、 [[状态模式]]和[[策略模式]] (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

你可以将[[../创建模式/抽象工厂模式]]和[[桥接模式]]搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

你可以结合使用[[../创建模式/生成器模式]]和[[桥接模式]]: 主管类负责抽象工作, 各种不同的生成器负责实现工作。