精准解读桥接模式-用桥接模式构建可扩展的软件系统

举报
dancer 发表于 2024/03/30 06:39:52 2024/03/30
【摘要】 桥接模式的目的是提高系统的灵活性和可扩展性。桥接模式的主要思想是将抽象和实现通过一个桥接类连接起来,从而实现它们的解耦。在这种模式中,抽象部分可以根据需要进行扩展,而实现部分可以自由地变化,而不影响彼此。桥接模式在处理多个独立变化的维度、解耦继承关系、处理平台差异和扩展现有系统等方面具有广泛的应用领域。通过使用桥接模式,可以提高系统的可维护性和可扩展性,使系统更加灵活和适应变化。

logo.png

💪🏻 制定明确可量化的目标,坚持默默的做事。


 一、了解桥接模式:探索抽象和实现的分离

1.1 开-闭原则

     “ 开~闭” 原则讲的是一个软件实体应当对扩展开放,对修改关闭。

     Softwareentitiesshouldbeopenforextension, but closedformodification.

        

     这个原则说的是,在设计 “个模块的时候,应当使这个模块可以在个被修改的前提下 被扩展。换言之,应当可以在不必修改源代码的情况 下改变这个模块的行为

         

1.2 组合/聚合复用原则

     合成/聚合复归原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新的对象通过向这些对象的委派达到复用已有功能的日的。

        

     这个设计原则有另一个更简短的表述:要尽量使用合成/聚合,尽量不要使用继承。

     更多软件设计原则见:深入理解面向对象设计的深层原则与思维

         

1.3 定义

     桥接模式是一种结构型设计模式,它的主要目的是将抽象部分与它的实现部分分离,以便它们可以独立地变化。桥接模式通过将继承关系转换为关联关系,实现了抽象部分与实现部分的解耦,从而使它们可以在系统中独立地变化,而不会相互影响。

桥接模式的定义包括以下几个要点:

  1. 抽象部分和实现部分的分离:桥接模式通过将抽象部分与它的实现部分分离,使得它们可以独立地变化。这样一来,抽象部分的变化对于实现部分是透明的,反之亦然。

  2. 组合关系:在桥接模式中,抽象部分和实现部分通过组合关系来连接,而不是通过继承关系。抽象部分包含对实现部分的引用,从而将两者关联起来。

  3. 解决多维度变化:桥接模式主要用于解决一个类存在两个独立变化的维度的情况,将其分别抽象出来,以便它们可以独立地变化。

     桥接模式通过将抽象部分和实现部分分离,以及通过组合关系来连接它们,实现了抽象部分与实现部分的解耦,从而提高了系统的灵活性和可扩展性。

         

1.4 用意

     桥梁模式的用意是“ 抽象化(Abstraction) 与实现化(Implementation)脱耦,使得 二者可以独立地变化

        

  抽象化

     一组对象如果具有相同的概念性联系,那么它们就可以通过一个共同的类来描述。如果 一些类具有相同的概念性联系,往往可以通过一个共同的抽象类来描述。 在更加复杂的情况下,可以使用 一个继承关系的包括抽象类和具体子类的等级结构来描 述。

  实现化

     抽象化给出的具体实现,就是实现化。

     一个类的实例就是这个类的实现化,一个具体子类是它的抽象超类的实现化。

     而在更加复杂的情况下,实现化也可以是与抽象化等级结构相平行的等级结构,同样可以由抽象 类和具体类组成。

  脱耦

     所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。

  • 强关联

     强关联就是在编译时期已经确定的,无法在运行时期动态改变的关联。

  • 弱关联

     弱关联就是可以动态地确定并且可以在运行时期动态地改变的关联。

        

     继承关系是强关联,而聚合关系是弱关联。

         

1.5 基本思想

     桥接模式的基本思想是通过将一个类的抽象部分和具体实现部分进行分离,使它们可以独立地变化,而不会相互影响。

     在桥接模式中,主要有两个关键角色:抽象部分和实现部分。抽象部分指的是需要进行抽象的概念、类或接口,而实现部分指的是抽象部分的具体实现或者具体类。

基本思想可以总结为以下几点:

  1. 将抽象部分和实现部分分离:桥接模式将一个类的抽象部分与它的实现部分分离开来,使得它们可以独立地变化。抽象部分通过抽象类或接口定义出需要的行为,而实现部分通过具体的实现类提供具体的实现。

  2. 通过组合关系连接抽象部分和实现部分:在桥接模式中,抽象部分和实现部分通过组合关系来连接。抽象部分中包含对实现部分的引用,从而将两者关联起来。

  3. 解耦抽象与实现:桥接模式将抽象部分和实现部分解耦,使得它们可以独立地变化。这样一来,对于抽象部分的变化对于实现部分是透明的,反之亦然。这种解耦的设计使得系统更加灵活,能够适应变化和扩展。

     桥接模式的基本思想在设计中是非常有用的,它可以帮助我们在面对多个维度的变化时,避免类爆炸的问题,并且提高了系统的可扩展性和维护性。

         

1.6 组成部分

桥接模式主要由以下几个组成部分构成:

  1. Abstraction(抽象部分):该部分定义了抽象类的接口,其中可能包括对实现部分的引用。通常情况下,抽象类中会含有对于实现层接口的引用,并且定义了一些基本的行为接口供子类实现。

  2. RefinedAbstraction(扩充抽象):该部分是抽象部分的子类,它扩展了抽象部分中定义的接口,提供了更为具体的行为。

  3. Implementor(实现部分接口):该部分定义了实现部分的接口,通常是一个接口或者抽象类。它仅提供了一个接口,用于定义实现部分的具体操作。

  4. ConcreteImplementor(具体实现部分):该部分是实现部分接口的具体实现,提供了实现部分的具体操作。

     在桥接模式中,Abstraction充当抽象部分的角色,它包含对Implementor的引用;RefinedAbstraction是Abstraction的子类,扩展了Abstraction中定义的接口;Implementor定义了实现部分的接口,而ConcreteImplementor是Implementor的具体实现。

这些组成部分共同协作,使得抽象部分和实现部分能够独立变化,而不会相互影响,从而提高了系统的灵活性和可扩展性。

         

1.7 桥梁模式的示意性系统的结构图

  图中两个等级结构:

  • 由抽象化角色和修正抽象化角色组成的抽象化等级结构。

  • 由实现化角色和两个具体实现化角色所组成的实现化等级结构。

  所涉及的角色:

  • 抽象化 (Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。

  • 修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。

  • 实现化 (Implementor)角色:这个角色给出实现化角色的接口 ,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上, 这两个接口可以非常不一样。 实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。

  • 具体实现化 (Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。

        

二、桥接模式的优势:构建灵活可扩展的软件系统

2.1 分离抽象和实现的好处

  1. 解耦抽象和实现:桥接模式通过分离抽象部分和实现部分,将它们进行解耦。这意味着抽象部分和实现部分可以独立地进行变化,互不影响。如果没有桥接模式,抽象部分和实现部分将紧密耦合在一起,任何一方的变化将会影响到另一方,导致系统难以维护和扩展。

  2. 支持多维度变化:桥接模式适用于那些具有多个独立变化维度的系统。通过桥接模式,可以将这些独立维度的变化分别抽象出来,形成不同的抽象部分和实现部分,从而支持这些维度的独立变化。这样一来,不仅系统更加灵活,还可以避免因为多个维度变化导致的类爆炸问题。

  3. 提高系统的扩展性:由于抽象部分和实现部分彼此独立,因此可以很方便地对系统进行扩展。当需要增加新的抽象或实现时,只需添加新的子类即可,而无需修改现有的代码。这样可以减少系统的变化范围,降低了引入新功能或变化的风险。

  4. 方便重用和维护:通过将抽象和实现分离,可以更好地组织和管理代码。抽象部分和实现部分之间的关系清晰明确,可以避免重复代码的产生。此外,由于变化的部分被抽象出来,当需要修改或维护时,只需关注相应的部分,而不会影响到其他部分,从而提高代码的可读性、可维护性和可重用性。

     桥接模式的分离抽象和实现的好处在于解耦、支持多维度变化、提高系统的扩展性,并且方便重用和维护代码。这使得系统更加灵活、可扩展和可维护,有助于构建高质量的软件系统。

         

2.2 解耦抽象和实现的优势

设计模式桥接模式的解耦抽象和实现的优势主要包括以下几点:

  1. 可以独立地进行抽象和实现的变化:通过桥接模式,我们可以将抽象部分和实现部分进行分离,并且它们可以独立地进行变化。这意味着我们可以修改抽象部分的实现,而无需影响到实现部分,反之亦然。这种解耦使得系统更加灵活,可以方便地适应变化和需求的变动。

  2. 避免类爆炸问题:在没有使用桥接模式的情况下,当一个类需要同时具有多个不同的特性或功能时,往往会导致类的数量急剧增加,即类爆炸问题。而桥接模式可以通过将这些特性或功能抽象出来,形成独立的抽象部分和实现部分,从而避免了类爆炸问题的产生。

  3. 增强系统的灵活性和可扩展性:通过桥接模式,我们可以很容易地将新的抽象部分和实现部分加入到系统中,而不会影响现有的代码。这些新增的部分可以通过继承原有的抽象类或接口,以及实现新的实现类或接口的方式来扩展。这样一来,系统变得更加灵活和可扩展,能够方便地支持新的特性或功能。

  4. 便于代码的重用和维护:桥接模式通过将抽象部分和实现部分分离,使得代码更加模块化和可重用。抽象部分和实现部分之间的关系清晰明确,可以减少重复代码的产生,并且提供了一个清晰的接口定义。这样一来,当需要修改或维护时,只需关注相应的部分,而不会影响到其他部分,从而提高了代码的可读性和可维护性。

     设计模式桥接模式的解耦抽象和实现的优势在于实现变化的隔离,避免类爆炸问题,增强系统的灵活性和可扩展性,以及便于代码的重用和维护。这使得系统更加灵活、可扩展和可维护,有助于构建高质量的软件系统。

         

2.3 提高可扩展性的实践案例举例

     桥接模式是一种用于将抽象与实现相分离的设计模式,通过将抽象和实现部分分开,使得它们可以独立地变化。这种模式的目的是增加系统的灵活性和可扩展性。

以下是一个桥接模式提高可扩展性的实践案例:

     假设我们有一个电商平台,该平台需要展示商品的详细信息,并且提供不同的商品展示风格。我们希望能够轻松地添加新的商品展示风格,而不需要修改已有的代码。

     首先,我们定义一个抽象的商品类(AbstractProduct),其中包含商品的共同属性和方法,例如名称、价格等。

     然后,我们创建一个具体的商品类(ConcreteProduct),继承自抽象商品类,实现商品类中的方法。

接下来,我们定义一个抽象的商品展示风格类(AbstractStyle),其中包含展示商品的方法。

     然后,我们创建一个具体的商品展示风格类(ConcreteStyle),继承自抽象商品展示风格类,实现展示商品的方法。在这个类中,我们可以自定义展示商品的样式和布局。

接着,我们引入桥接模式,创建一个桥接类(Bridge),该类将抽象商品类和抽象商品展示风格类作为成员变量。

     最后,我们可以通过使用桥接模式,将具体的商品类和具体的商品展示风格类进行组合,从而创建不同样式的商品展示界面。

通过这种方式,我们可以很方便地新增商品展示风格,只需要创建一个新的具体商品展示风格类,并将其与已有的商品类进行组合,而不需要改动已有的代码。

     这样,我们就能够以一种可扩展的方式,增加系统的灵活性,并且能够方便地适应不同的需求和变化。

        

三、桥接模式的应用场景:实例解析

3.1 场景

     企业系统发消息提醒。

        

     业务消息有 普通消息、加急消息 和 特急消息 等多种。

     消息类型有 内部消息、邮件消息 和 手机短信消息 待多种。

         

3.2 不用设计模式方案

3.2.1 简化功能实现及结构图

     先实现普通消息。

     发送方式有 内部消息 和 邮件消息。

        

     发普通消息舒服两种不同的实现方式,为了让外部能统一操作,因此把消息设计成接口,然后由两个不同的实现类分别实现系统内部消息方式和邮件消息的方式。

  系统结构示意图如下

3.2.2 添加加急消息实现及结构图

     这时添加加急消息的功能,也有两种发送的方式,同样是内部消息方式和邮件消息的方式。

     加急消息不同于普通消息,加急消息会自动在消息上添加加急,然后再发送消息;加急消息添加额外的功能比如监控消息的处理情况等。

     因此加急消息需要扩展同一个新的接口,除了基本的发送消息的功能,还需要添加监控的功能。

  系统结构示意图如下

3.2.3 添加特急消息实现及结构图

     特急消息可能不是监控,还是另外一种业务需求。

        

     特急消息跟加急消息一样,除了基本的发送消息的功能,还需要添加额外的功能。

  系统结构示意图如下

3.2.4 添加新的发送消息方式及结构图

     从上面步骤发现,这时要添加一种新的发送消息方式,是需要在每一个抽象的具体实现中都需要添加发送手机短信消息的处理实现。即发内部消息、加急消息和特急消息的处理,都可以通过手机来发送。这意味着,需要添加三个实现。

  系统结构示意图如下

         

3.3 问题 

     从添加一种新消息类型手机短信消息开始发现有点不太好扩展了。


  总结:        

     采用通过继承来扩展的实现方式,有个明显的缺点,扩展消息的种类不太容易。不同种类的消息具有不同的业务(即不同的实现),在这种情况下,每个种类的消息,需要实现所有不同的消息发送方式。

     要是考虑业务功能上再扩展一下会如何?比如,要求实现群发消息的功能,也就是一次可以发送多条消息,这就意味着很多地方都得修改,这是非常痛苦的。

        

     这时需要一种实现方式即实现功能,又能灵活地扩展。

         

3.4 解决方案:桥接模式

3.4.1 分析

     仔细分析上面的示例,根据示例的功能要求,示例的变化具有两个纬度,一个纬度是抽象的消息这边,包括内部消息、加急消息和特急消息,这几个抽象的消息本身就具有一定的关系,加急消息和特急消息会扩展普通消息;另一个纬度是在具体的消息发送方式上,包括站内部消息、E-mail 和手机短信息,这几个方式是平等的,可被切换的方式。这两个纬度一共可以组合出9 种不同的可能性来。

  9 种不同的可能性:

3.4.2 原因

     问题的原因是在于消息的抽象和实现是混杂在一起的,这就导致了一个纬度的变化会引起另一个纬度进行相应的变化,从而使得程序扩展起来非常的困难。

3.4.3 解决思路

     必须把这两个纬度分开,也就是将抽象部分和实现部分分开, 让它们相互独立,这样就可以实现独立的变化,使扩展变得简单。

        

     桥接模式通过引入实现的接口,把实现部分从系统中分离出去。那么抽象这边如何使用具体的实现呢?肯定是用面向实现的接口来编程了,为了让抽象这边能够很方便地与实现结合起来,把顶层的抽象接口改成抽象类,在其中持有一个具体的实现部分的实例

        

     这样一来,对于需要发送消息的客户端而言,就只需要创建相应的消息对象,然后调用这个消息对象的方法就可以 了,这个消息对象会调用持有的真正的消息发送方式来把消息发送出去。也就是说客户端只是想要发送消息而已,并不想关心具体如何发送。

3.4.4 解决结构图


  • Abstraction:抽象部分的接口:通常在这个对象中,要维护一 个实现部分的对象引用,抽象对象里面的方法,需要调用实现部分的对象来完成。这个对象中的方法,通常都是和具体的业务相关的方法。

  • RefinedAbstraction:扩展抽象部分的接口。通常在这些对象中,定义跟实际业务相关的方法,这些方法的实现通常会使用Abstraction 中定义的方法,也可能需要调用实现部分的对象来完成。

  • Implementor:定义实现部分的接口。这个接口不用和 Abstraction 中的方法一致,通常是由Implementor 接口提供基本的操作。而 Abstraction 中定义的是基于这些基本操作的业务方法,也就是说Abstraction 定义了基于这些基本操作的较高层次的操作

  • Concretelmplementor:真正实现Implementor 接口的对象。

3.4.5 解决示例代码

  定义Implementor接口

/**
 * 定义实现部分的接口,可以与抽象部分接口的方法不一样
 */
public interface Implementor {
    /**
     * 示例方法,实现抽象需要的某些具体功能
     */
    public void operationImpl();
}

  定义抽象类Abstraciton类

/**
 * 定义抽象部分的接口
 */
public abstract class Abstraction {
    /**
     * 示例方法,实现抽象需要的某些具体功能
     */
    protected Implementor impl;
    /**
     * 构造方法,传入实现部分的对象
     * @param impl 实现部分的对象
     */
    public Abstraction(Implementor impl) {
        this.impl = impl;
    }
    /**
     * 示例操作,实现一定的功能,可能需要转调部分的具体实现方法
     */
    public void operation() {
        impl.operationImpl();
    }
}
  具体的实现
/**
 * 真正的具体实现对象
 */
public class ConcreteImplementorA implements Implementor {
    
    public void operationImpl () {
        // 真正的具体实现
    }
}

/**
 * 真正的具体实现对象
 */
public class ConcreteImplementorB implements Implementor {
    
    public void operationImpl () {
        // 真正的具体实现
    }
}
  扩展Abstraction抽象类的对象实现
/**
 * 扩充由Abstraction定义的接口功能 
 */
public class RefinedAbstraction extends Abstraction {


    public RefinedAbstraction(Implementor impl) {
        super (impl) ; 
    }
    /**
     * 示例操作,实现一定的功能 
     */
    public void otherOperation () {
        // 实现一定的功能,可能会使用具体实现部分的实现方法
        // 但是本方法更大的可能是使用Abstraction 中定义的方法
        // 通过组合使用Abstraction 中定义的方法来完成更多的功能
    }
}

3.5 使用桥接模式实现消息系统方案

     使用桥接模式来实现消息系统,看能否解决“即能方便地实现功能,又能有很好的扩展性”?

3.5.1 思路

  • 抽象部分和实现部分分离,分析要实现的功能。抽象部分就是各个消息的 类型所对应的功能,而实现部分 就是各种发送消息的方式。

  • 其次要按照桥接模式的结构,给抽象部分和实现部分分别定义接口,然后分别实现 它们就可以 了

3.5.2 实现步骤及结构图

    从简单的功能开始,一步一步的扩展功能,看能否实现“即能方便地实现功能,又能很好的扩展功能”。

        

     先实现普通消息 和 加急消息的功能,发送方式先实现内部消息和 Email消息两种。

  简单功能结构图

3.5.3 实现代码

  定义消息方式接口

/**
 * 实现发送消息的统一接口 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:04:39
 */
public interface MessageImplementor {
    /**
     * 发送消息
     * @param message 要发送的消息内容
     * @param toUser 消息发送的目的人员
     */
    public void send(String message, String toUser);
}
  定义具体的发送消息实现类-内部消息
/**
 * 以内部消息的方式发送消息 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:08:12
 */
public class MessageSms implements MessageImplementor {
    @Override
    public void send(String message, String toUser) {
        System.out.println("使用内部消息的方式,发送消息'"+ message +"' 给" + toUser);
    }
}
  定义具体的发送消息实现类-Email消息
/**
 * 以E-mail的方式发送消息 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:09:30
 */
public class MessageEmail implements MessageImplementor {
    @Override
    public void send(String message, String toUser) {
        System.out.println("使用E-mail消息的方式,发送消息'"+ message +"' 给" + toUser);
    }
}
  定义抽象类
/**
 * 抽象的消息对象 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:06:06
 */
public abstract class AbstractMessage {

    protected MessageImplementor impl;

    /**
     * 构造方法,传入实现部分的对象
     * @param impl 实现部分的对象
     */
    public AbstractMessage(MessageImplementor impl) {
        this.impl = impl;
    }

    public void sendMessage(String message, String toUser) {
        this.impl.send(message, toUser);
    }
}
  扩展抽象类-内部消息的实现
/**
 * 普通内部消息 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:10:20
 */
public class CommonMessage extends AbstractMessage {
    /**
     * 构造方法,传入实现部分的对象
     * @param impl 实现部分的对象
     */
    public CommonMessage(MessageImplementor impl) {
        super(impl);
    }

    public void sendMessage(String message, String toUser) {
        // 对于普通内部消息,什么都不干,直接调用父类的方法,把消息发送出去就可以了
        super.sendMessage(message, toUser);
    }
}
  扩展抽象类-加急消息的实现
/**
 * 加急消息类 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:11:35
 */
public class UrgencyMessage extends AbstractMessage {
    /**
     * 构造方法,传入实现部分的对象
     *
     * @param impl 实现部分的对象
     */
    public UrgencyMessage(MessageImplementor impl) {
        super(impl);
    }

    @Override
    public void sendMessage(String message, String toUser) {
        message = "加急: " + message;
        super.sendMessage(message, toUser);

    }

    public Object watch(String messageId) {
        // 获取相应的数据,组织成监控的数据对象,然后返回
        return null;
    }
}
  测试客户端
/**
 * 测试客户端 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:14:08
 */
public class Client {

    public static void main(String[] args) {

        // 发一个普通的email消息
        MessageImplementor implementor = new MessageEmail();
        AbstractMessage message = new CommonMessage(implementor);
        message.sendMessage("这是一条普通消息", "张三");
        System.out.println("------------------------");

        // 发一个普通的内部消息
        MessageImplementor implementor2 = new MessageSms();
        AbstractMessage message2 = new CommonMessage(implementor2);
        message2.sendMessage("这是一条普通消息", "张三");
        System.out.println("------------------------");

        // 加急消息
        MessageImplementor implementor21 = new MessageSms();
        AbstractMessage message21 = new UrgencyMessage(implementor21);
        message21.sendMessage("这是一条普通消息", "张三");
        System.out.println("------------------------");

        // 加急消息
        MessageImplementor implementor22 = new MessageEmail();
        AbstractMessage message22 = new UrgencyMessage(implementor22);
        message22.sendMessage("这是一条普通消息", "张三");
        System.out.println("------------------------");

    }
}
  运行结果如下:
使用E-mail消息的方式,发送消息'这是一条普通消息' 给张三
------------------------
使用内部消息的方式,发送消息'这是一条普通消息' 给张三
------------------------
使用内部消息的方式,发送消息'加急: 这是一条普通消息' 给张三
------------------------
使用E-mail消息的方式,发送消息'加急: 这是一条普通消息' 给张三
------------------------

3.5.4 扩展-添加新功能-特急消息及结构图

     只需要在抽象部分再添加一个特急消息的类,扩展抽象消息就可以把特急消息的处理功能加入到系统中。

  添加特急消息后的结构图

  扩展抽象类-特急消息的实现

/**
 * 发送特急消息 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:11:35
 */
public class SpecialUrgencyMessage extends AbstractMessage {
    /**
     * 构造方法,传入实现部分的对象
     *
     * @param impl 实现部分的对象
     */
    public SpecialUrgencyMessage(MessageImplementor impl) {
        super(impl);
    }

    @Override
    public void sendMessage(String message, String toUser) {
        message = "特急: " + message;
        super.sendMessage(message, toUser);
        // 还需要添加一些特急消息的额外业务逻辑
    }

    public Object hurry(String messageId) {
        // 执行特急的业务逻辑
        return null;
    }
}
  测试客户端
/**
 * 测试客户端 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:14:08
 */
public class Client {

    public static void main(String[] args) {

        // 发一个特急的内部消息
        MessageImplementor implementor1 = new MessageSms();
        AbstractMessage message1 = new SpecialUrgencyMessage(implementor1);
        message1.sendMessage("这是一条消息", "张三");
        System.out.println("------------------------");
        // 发一个特急的email消息
        MessageImplementor implementor2 = new MessageEmail();
        AbstractMessage message2 = new SpecialUrgencyMessage(implementor2);
        message2.sendMessage("这是一条消息", "张三");
        System.out.println("------------------------");

    }
}
  • 修改的部分:抽象类的实例化为特级类即可。(AbstractMessage message1 = new SpecialUrgencyMessage(implementor1);)

  运行结果如下:

使用内部消息的方式,发送消息'特急: 这是一条消息' 给张三
------------------------
使用E-mail消息的方式,发送消息'特急: 这是一条消息' 给张三
------------------------

3.5.5 扩展-添加新功能-手机短信消息方式及结构图

     在实现部分新增加一个实现类,实现用手机发送消息的方式就可以 了。

  添加手机短信消息方式后的结构图

  扩展消息接口-手机短信消息的实现

/**
 * 以手机短信的方式发送消息 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:09:30
 */
public class MessageMobile implements MessageImplementor {
    @Override
    public void send(String message, String toUser) {
        System.out.println("使用手机短信发消息的方式,发送消息'"+ message +"' 给" + toUser);
    }
}

  测试客户端

/**
 * 测试客户端 <br/>
 *
 * @author danci_
 * @date 2024/2/1 00:14:08
 */
public class Client {

    public static void main(String[] args) {

        // 发一个特急的内部消息
        MessageImplementor implementor1 = new MessageMobile();
        AbstractMessage message1 = new SpecialUrgencyMessage(implementor1);
        message1.sendMessage("这是一条消息", "张三");
        System.out.println("------------------------");

    }
}
  •  修改的部分:接口的实例化为手机短信实现类即可。(MessageImplementor implementor1 = new MessageMobile();)

  运行结果如下:

使用手机短信发消息的方式,发送消息'特急: 这是一条消息' 给张三
------------------------

3.6 总结

     不用模式的实现痛点:添加手机短信方式时,需要3.2.4 图中添加三个手机消息的实现类。

     用模式的实现方式:如3.5.5 只需要添加手机消息方式的实现类即可。

        

     这么简单?好像看起来完全没有了前面所提到的问题。地确如此,采用桥接模式来实现,抽象部分和实现部分分离开了,可以相互独立地变化,而不会相互影响。因此在抽象部分添加新的消息处理,对发送消息的实现部分是没有影响的;反过来增加发送消息的方式,对消息处理部分也是没有影响的。

四、桥接模式的实践指南:如何应用于实际开发中

4.1 什么是桥接

     所谓桥接,通俗点说就是在不同的东西之间搭一 个桥,让它们能够连接起来,可以相互通讯和使用。

        

     那么在桥接模式中到底是给什么东 西来搭桥呢?就是为被分离 了的抽象部分和实现部分来搭桥,比如前面示例中在抽象的消息和具体消息发送之间搭个桥。

     注:在桥接模式中的桥接是单向的,也就是只能是抽象 部分的对象去使用具体实现部分的对象,而不能反过来,也就是个单向桥。

4.2 何时选用桥接模式

  • 如果你不希望在抽象部分和实现部分采用固定的绑定关系,可以采用桥接模式, 来把抽象部分和实现部分分开,然后在程序运行期间来动态地设置抽象部分需 要用到的具体的实现,还可以动态地切换具体的实现。

  • 如果出现抽象部分和实现部分都能够扩展的情况,可以采用桥接模式,让抽象部分和实现部分独立地变化,从而灵活地进行单独扩展,而不是搅在一起,扩展一边就会影响到另一边 。

  • 如果希望实现部分的修改不会对客户产生影响,可以采用桥接模式。由 于客户是面向抽象的接口在运行,实现部分的修改可以独立于抽象部分,并不会对客户产生影响,也可以说对客户是透明的。

  • 如果采用继承的实现方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看是否能分离成不同的纬度,然后通过桥接模式来分离它们,从而减少子类的数目。

4.3 为何需要桥接

     为了达到让抽象部分实现部分都可以独立变化的目的,在桥接模式中,是把抽象部分和实现部分分离开来的,虽然从程序结构上是分开了,但是在抽象部分实现的时候, 还是需要使用具体的实现的,这可怎么办呢?抽象部分如何才能调用到具体实现部分的 功能呢?很简单,搭个桥就可以了。搭个桥,让抽象部分通过这个桥就可以调用到实现部分的功能 了,因此需要桥接。

4.4 如何桥接

     这个在理解上也很简单,只要让抽象部分拥有实现部分的接口对象,就桥接上了, 在抽象部分即可通过这个接口来调用具体实现部分的功能。也就是说,桥接在程序上体现了在抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系。

4.5 独立变化

     桥接模式的意图是使得抽象和实现可以独立变化,都可以分别扩充。也就是说抽象 部分和实现部分是一种非常松散的关系。

     从某个角度来讲,抽象部分和实现部分是可以完全分开的,独立的,抽象部分不过是一个使用实现部分对外接口的程序罢了。

        

     那抽象和实现为何还要组合在一起呢?原因是在抽象部分和实现部分还是存在内部 联系的,抽象部分的实现通常是需要调用实现部分的功能来实现的。

        

4.6 动态变换功能

     由于桥接模式中的抽象部分和实现部分是完全分离的,因此可以在运行时动态组合 具体的真实实现,从而达到动态变换功能的目的。

        

     从另外一个角度看,抽象部分和实现部分没有固定的绑定关系,因此同一个真实实现可以被不同的抽象对象使用;反过来,同一个抽象也可以有多个不同的实现。就像前面示例的那样,比如,内部消息的实现功能,可以被普通消息、加急消息或是特急消息等不同的消息对象使用;反过来,某个消息具体的发送方式,可以是内部消息或者是E-mail,也可以是手机短消息等具体的发送方式。

4.7 桥接模式和继承

     继承是扩展对象功能的一种常见手段,通常情况下,继承扩展的功能变化纬度都是一纬的,也就是变化的因素只有一类。

     对于出现变化因素有两类的,也就是有两个变化纬度的情况,继承实现就会比较痛苦。比如上面的示例,就有两个变化纬度, 一个是消息的类别,不同的消息类别处理不同;另外一 个是消息的发送方式。

        

     桥接模式解决因多纬度继承的实现比较痛苦问题。

     桥接模式就是用来解决这种有两个变化纬度的情况下,如何灵活地扩展功能的一个很好的方案。其实,桥接模式主要是把继承改成了使用对象组合,从而把两个纬度分开,让每一个纬度单独去变化,最后通过对象组合的方式,把两个纬度组合起来,每一种组合的方式就相当于原来继承中的一种实现,这样就有效地减少了实际实现的类的个数。(这里结合“组合/聚合复用原则”理解,见1.2

        

     从理论上来说,如果用桥接模式的方式来实现这种有两个变化纬度的情况,最后实 际的实现 类应该是两个纬度 上可变数量的和那么多个。同样是上面那个不例,使用桥接模式来实现,实现全的话,最后需要的实现类的数目应该是: 3 + 3 =6 个 。

     这也从侧面体现了,使用对象组合的方式比继承要来得更灵活。

4.8 谁来桥接

     就是谁来负责创建抽象部分和实现部分的关系。

     说得更直白点,就 是谁来负责创建Implement or 对象,并把它设置到抽象部分的对象中去,这点对于使用桥 接模式来说,是十分重要的 一点。

  大致有如下几种实现方式

  • 由客户端负责创建Implementor 对象,并在创建抽象部分对象的时候,把它设置到抽象部分的对象中去,前面的示例采用的就是这个方式。
  • 可以在抽象部分对象构建的时候,由抽象部分的对象自己来创建相应的 Implementor 对象,当然可以给它传递一些参数,它可以根据参数来选择并创建具体的Implementor 对象。
  • 可以在Abstraction中选择并创建一个默认的Implementor 对象,然后子类可以根据需要改变这个实现。
  • 也可以使用抽象工厂或者简单工厂来选择并创建具体的Implementor 对象,抽 象部分的类可以通过调用工厂的方法来获取Implementor 对象。
  • 如果使用 IoC/DI 容器的话,还可以通过 IoC/DI 容器来创建具体的 Implementor 对象,并注入回到Abstraction 中。

  实现中大致有如下改变

  • 原来protected 的Messagelmplementor 类型的属性,不需要了,去掉。
  • 提供一个protected 的方法来获取要使用的实现部分的对象,在这个方法中,根据消息的长度来选择合适的实现对象。
  • 构造方法什么都不用做了,也不需要传入参数。
  • 在原来使用impl 属性的地方,要修改成通过上面那个方法来获取合适的实现对象了,不能直接使用impl 属性,否则会没有值。

4.9 实践中的注意事项和经验分享

在使用桥接模式时,以下是一些注意事项和经验分享:

  1. 确定变化的维度:在使用桥接模式之前,需要仔细分析和确定系统中可能存在的多个变化维度。这些变化维度应该是相互独立且可能扩展的,才适合使用桥接模式。

  2. 优先选择桥接模式:当系统中的抽象和实现部分需要灵活地变化,而且不希望它们耦合在一起时,可以考虑使用桥接模式。它能够解耦抽象和实现,并使它们可以独立地变化。

  3. 使用抽象接口:在桥接模式中,定义一个抽象接口来描述抽象部分的操作,这样可以使得实现部分可以独立地变化。抽象接口应该能够满足不同实现类的需求,并且不应该与具体实现相关。

  4. 引入桥接类:桥接模式通过引入一个桥接类来连接抽象部分和实现部分。这个桥接类应该具备将抽象和实现部分联系起来的功能,并提供一致的接口给客户端使用。

  5. 合理组织类的结构:在桥接模式中,可以采用多种方式组织类的结构,例如可以使用继承和实现关系、聚合关系等。选择合适的组织方式可以让系统更加清晰和易于理解。

  6. 考虑扩展性:桥接模式非常适合处理系统的扩展性问题。通过将抽象和实现部分分离,可以很容易地对它们进行扩展和新增。在设计过程中,要考虑到系统可能的变化和扩展需求,以便更好地应对未来的变化.

     尽管桥接模式可以提高系统的灵活性和可扩展性,但也要避免滥用。过度使用桥接模式可能会导致系统变得复杂,增加开发成本。因此,在使用桥接模式时,要根据具体的需求和系统设计目标来判断是否适合使用,并合理权衡其优缺点。

五、桥接模式的优缺点

5.1 优点

  • 分离抽象和实现部分

    桥接模式分离了抽象部分和实现部分,从而极大地提高了系统的灵活性。让抽象部分和实现部分独立开来,分别定义接口,这有助于对系统进行分层,从而产生更好的结构化的系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。

  • 更好的扩展性

    由于桥接模式把抽象部分和实现部分分离开了,而且分别定义接口,这就使得抽象部分和实现部分可以分别独立地扩展,而不会相互影响,从而大大地提高了系统的可扩展性。

  • 可动态地切换实现

    由于桥接模式把抽象部分和实现部分分离开了,所以在实现桥接的时候,就可以实现动态的选择和使用具体的实现。也就是说一个实现不再是固定的绑定在一个抽象接口上了,可以实现运行期间动态地切换。

  • 可减少子类的个数

    根据前面的讲述,对于有两个变化纬度的情况,如果采用继承的实现方式,大约需要两个纬度上的可变化数量的乘积个子类;而采用桥接模式来实现,大约需要两个纬度上的可变化数量的和个子类。可以明显地减少子类的个数。

5.2 缺点

     桥接模式虽然能够有效地解耦抽象和实现部分,提高系统的灵活性和扩展性,但也存在一些缺点,包括:

  1. 增加复杂性:引入桥接模式会增加系统的复杂性,需要定义更多的类和接口来描述抽象和实现的关系。这可能会增加开发和维护的难度,特别是针对较小规模的系统而言,引入桥接模式可能会显得过度。

  2. 关注点分散:使用桥接模式会导致抽象和实现部分的代码分散在不同的类中,可能会增加系统的理解难度。开发人员需要在多个类之间进行协作,可能会导致团队沟通成本增加。

  3. 需要深思熟虑的设计:桥接模式需要事先对系统中可能存在的多个变化维度进行深入的分析和设计,这要求设计者对系统有很深入的了解和抽象能力。如果变化维度的抽象和实现并不清晰,可能会导致桥接模式的设计变得复杂和困难。

  4. 增加运行开销:虽然桥接模式可以提高系统的灵活性,但引入桥接模式可能会增加运行时的开销。因为桥接模式引入了额外的抽象层,可能会导致一定程度的性能损失。

     总之,桥接模式虽然有利于系统的灵活性和扩展性,但在应用时需要谨慎考虑。开发者需要权衡桥接模式带来的优点和缺点,并结合具体的业务场景和系统需求来决定是否使用桥接模式。

六、桥接模式的未来展望:趋势与发展

6.1 桥接模式的现状和应用领域

桥接模式现状和应用领域如下:

现状:

  1. 桥接模式是一种经典的设计模式,被广泛应用于软件开发领域。
  2. 桥接模式在许多开发框架和库中都得到了应用,例如Java中的AWT和Swing。
  3. 桥接模式与其他设计模式相结合,可以提高系统的可扩展性、灵活性和可维护性。

应用领域:

  1. 当一个系统具有多个独立变化的维度时,可以使用桥接模式来分离它们。例如,一个电商平台可以使用桥接模式将商品类与商品展示风格类分离。
  2. 桥接模式可以用于处理多层次的继承关系,避免类的爆炸性增长。例如,一个图形绘制系统可以使用桥接模式来处理多种不同的绘制工具和形状。
  3. 桥接模式可以用于处理不同平台或技术之间的差异。例如,一个跨平台的移动应用可以使用桥接模式来处理不同操作系统和设备的兼容性。
  4. 桥接模式可以用于扩展现有的系统,使其能够适应新的需求和变化。例如,一个音频播放器可以使用桥接模式来支持不同的音频格式。

     桥接模式在处理多个独立变化的维度、解耦继承关系、处理平台差异和扩展现有系统等方面具有广泛的应用领域。它能够提高系统的灵活性、可扩展性和可维护性,使系统更加容易开发和维护。

6.2 桥接模式在面向对象设计中的进展

     桥接模式是面向对象设计中的经典设计模式之一,它的出现和发展对面向对象设计有着积极的影响。在面向对象设计的进展中,桥接模式扮演了以下角色:

  1. 解耦抽象和实现:桥接模式的主要作用是解耦抽象和实现部分,使它们可以独立地变化。这一设计理念促进了面向对象设计中的模块化、低耦合的设计原则。通过桥接模式,设计者可以更好地遵循“开闭原则”,即对扩展开放,对修改封闭。

  2. 处理多维度变化:面向对象设计中经常需要处理不同维度的变化,桥接模式很好地解决了这一问题。它为设计者提供了一种清晰的思路,使得设计者能够更容易地应对系统中存在的多个变化维度,从而提高了系统的灵活性和可维护性。

  3. 促进抽象与实现的设计思维:桥接模式要求设计者能够清晰地区分抽象和实现,设计出良好的桥接类来连接它们。这种设计思维有利于培养设计者的抽象能力和系统设计能力,同时也促进了面向对象设计思想的发展。

  4. 与其他设计模式结合应用:桥接模式也促进了面向对象设计模式的发展。它常常与其他设计模式结合使用,如工厂方法模式、装饰器模式等,共同为系统的灵活性和扩展性提供支持。

  5. 提高系统的扩展性:桥接模式允许抽象和实现部分可以独立地扩展和变化,从而大大提高了系统的扩展性。通过定义抽象接口和实现类之间的桥接关系,可以在不修改原有代码的情况下,新增新的抽象和实现类,并通过组合关系将它们组合起来使用。这种灵活的扩展方式有助于应对系统需求的变化。

  6. 改善系统的可维护性:桥接模式使得抽象和实现部分相互独立,减少了它们之间的直接关联,使系统更加灵活。这样,在维护和修改系统时,只需要关注这两个部分的变化,而不会对其他部分造成影响。这有助于降低系统的复杂性,提高代码的可读性和可维护性。

  7. 促进代码重用:桥接模式通过将抽象和实现分离,使得它们可以以不同的组合方式进行复用。具体而言,通过定义一个抽象接口和一个实现类,可以分别派生出不同的子类来实现各自的功能。这种灵活的组合方式可以提高代码的重用性,并降低代码的冗余度。

  8. 降低开发的复杂度:桥接模式允许设计者专注于抽象和实现两个部分的设计,而无需关注它们之间的具体实现细节。通过将两者解耦,桥接模式简化了系统的设计和开发过程,降低了系统的复杂性,使得设计者可以更加专注于精炼和优化相关部分的代码。

     总的来说,桥接模式在面向对象设计中的进展是积极的。它推动了面向对象设计的模块化、低耦合、高内聚等设计原则的发展,为设计者提供了一种有效的思路来处理系统中存在的多维度变化问题。同时,它也促进了设计模式的系统化和结构化应用,为面向对象设计提供了更加丰富和灵活的设计思想。

6.3 前景与展望:如何更好地应对软件演变和需求变化

桥接模式在软件演变和需求变化方面具有以下几个方面的影响和作用:

  1. 支持系统的扩展和变化:桥接模式通过将抽象和实现分离,使得它们可以独立地变化和演化。当系统需要新增新的抽象或实现时,可以通过派生新的子类来扩展,而无需修改已有的代码。这种灵活性使得系统能够更好地适应需求的变化,并支持软件的演变。

  2. 提高系统的灵活性和可维护性:桥接模式通过解耦抽象和实现部分,将它们之间的耦合关系转化为聚合关系,降低了系统的复杂度。这种解耦使得系统更加灵活,可以更方便地进行组合和替换各个部分。同时,这种解耦也提高了系统的可维护性,因为修改一个部分不会对其他部分造成影响。

  3. 适应多维度的变化:桥接模式能够很好地应对系统中存在的多个变化维度。例如,当系统需要同时支持不同的操作系统和不同的文件系统时,可以使用桥接模式将抽象和实现进行分离,使得它们可以独立地进行变化和演化。通过定义桥接类来连接不同的实现,系统可以灵活地适配各种组合,以满足不同维度变化的需求。

  4. 提升代码重用和可读性:桥接模式通过将抽象和实现分离,使得它们可以以不同的组合方式进行复用。具体而言,可以通过定义一个抽象接口和多个实现类,分别派生出不同的子类来实现各自的功能。这种灵活的组合方式可以提高代码的重用性,并降低代码的冗余度。同时,由于桥接模式保持了代码的清晰结构,提高了代码的可读性和可维护性。

  5. 易于维护和扩展:由于桥接模式将抽象和实现部分解耦,系统在需求变化时可以更容易地进行扩展和维护。新的抽象和实现可以独立地进行修改和扩展,无需影响到其他部分,从而减少了演变过程中的风险。

  6. 提高系统的可定制性:桥接模式将抽象和实现部分分离,使得系统的不同部分可以独立地进行定制和配置。这意味着系统可以根据具体需求进行定制,灵活地实现符合特定要求的功能,从而更好地满足客户需求。

  7. 降低耦合度:桥接模式通过将抽象和实现部分分离,减少了它们之间的直接依赖关系,从而降低了系统的耦合度。在软件演变和需求变化过程中,降低耦合度可以降低修改的影响范围,使得系统更容易维护和调整。

  8. 有利于并行开发:通过桥接模式的设计,不同的抽象和实现部分可以并行开发和测试,而无需等待其他部分的完成,提高了并行开发的效率。这对于软件演变和需求变化时的快速迭代非常有益。

     总的来说,桥接模式在软件演变和需求变化中能够提供灵活性、扩展性和可维护性。它通过解耦抽象和实现部分,以及定义灵活的组合方式,使得系统能够更好地适应变化并满足新的需求。这使得桥接模式在面对需求的不断变化和系统的演变时,可以提供一种可靠的设计方案。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。