设计模式-桥接模式

33 阅读6分钟

# 桥接模式

1.简介

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

假如我们有一个几何(形状)类,它可以扩展出两个子类---->圆形和长方形。但如果现在你希望她可以包含颜色,那么你现在需要再创建红色、蓝色的子类。这个时候你总共需要创建四个子类才可以覆盖所有组合。(红色圆形、红色长方形、蓝色圆形、蓝色长方形)在后面的扩展中这种设计方式将会形成几何式的增长。

其实问题的根本原因在于我们是在两个独立的维度上通过继承的形式扩展形状类。桥接模式可以讲继承改为组合的方式解决这个问题,抽取其中一个维度成为独立的类层次,再在初始类中引用这个新层次对象,从而使一个类不必拥有所有的状态和行为。

也就是说我们将颜色相关的代码抽取为一个类,再在形状类中添加一个指向某一颜色对象的引用成员变量,现在形状类就可以将所有与颜色相关的工作委派给连入的颜色对象,这种引用就成为了形状和颜色之间的桥梁。后续如果新增颜色也不需要修改形状类的代码。

2.UML图

桥接模式1.png

  • 抽象化角色(Abstraction): 抽象化给出的定义,并保存一个对实现化对象的引用。
  • 修正抽象化角色(RefinedAbstraction): 扩展抽象化角色,改变和修正父类对抽象化的定义。
  • 实现化角色(Implementor): 这个角色给出实现化角色的接口,但不给出具体的实现。这个接口不一定和抽象化角色的接口定义相同,实现化角色只给出底层操作,抽象化角色应当只给出高于底层的操作。
  • 具体实现化角色(ConcreteImplementor): 这个角色给出实现化角色接口的具体实现。

3.代码实现

假设现在有一个做咖啡的需求,最开始需求只有一种杯型(正常杯),原味或者加糖这两种选择。于是我们开始开发需求:

​ 首先定义一个点咖啡的接口,里面有一个下单咖啡的方法,至于具体哪种咖啡由子类去定义:

package com.gs.designmodel.bridge.common;

/**
 * @author: Gaos
 * @Date: 2023-08-09 16:20
 *
 **/
public interface ICoffee {

    /**
     * 下单咖啡
     * @param count 数量
     */
    void orderCoffee(int count);
}

​ 原味咖啡类:

package com.gs.designmodel.bridge.common;

/**
 * @author: Gaos
 * @Date: 2023-08-09 16:20
 *
 * 原味咖啡
 **/
public class CoffeeOriginal implements ICoffee{

    @Override
    public void orderCoffee(int count) {
        System.out.println("原味咖啡" + count + "杯");
    }
}

​ 加糖咖啡类:

package com.gs.designmodel.bridge.common;

/**
 * @author: Gaos
 * @Date: 2023-08-09 16:21
 *
 * 加糖咖啡
 **/
public class CoffeeWithSugar implements ICoffee{
    @Override
    public void orderCoffee(int count) {
        System.out.println("加糖咖啡" + count + "杯");
    }
}

​ 这个时候需求突然变更了,客户准备增加两个容量规格的咖啡----->大杯和小杯。在目前的代码设计结构下,这意味着你需要增加几个相对应地实现类。

​ 中杯加糖:

package com.gs.designmodel.bridge.common;

/**
 * 中杯加糖
 */
public class MiddleCoffeeWithSugar implements ICoffee {

    @Override
    public void orderCoffee(int count) {
        System.out.println("中杯加糖" + count + "杯");
    }
}

​ 大杯加糖:

package com.gs.designmodel.bridge.common;

/**
 * @author: Gaos
 * @Date: 2023-08-09 16:24
 **/
public class LargeCoffeeWithSugar implements ICoffee{
    @Override
    public void orderCoffee(int count) {
        System.out.println("大杯加糖咖啡" + count + "杯");
    }
}

​ 小杯加糖:

package com.gs.designmodel.bridge.common;

/**
 * @author: Gaos
 * @Date: 2023-08-09 16:25
 **/
public class SmallCoffeeWithSugar implements ICoffee{
    @Override
    public void orderCoffee(int count) {
        System.out.println("小杯加糖咖啡" + count + "杯");
    }
}

............

加着加着你可能感觉到了不对劲。这一共需要6个类,但是如果又要更改各种维度的需求,那类的个数将会呈指数级增长。

在这个场景下我们对代码进行重构,目前需求中有两个变化维度,咖啡的容量和口味,都需要独立变化。如果使用上面继承的方式,那么变化类就会指数增加。也可以将容量理解为抽象部分( Abstraction),口味理解为实现部分( Implementor),对这两个部分进行桥接。

  • 创建实现化部分接口定义(咖啡口味维度):
package com.gs.designmodel.bridge.special;

/**
 * @author: Gaos
 * @Date: 2023-08-09 19:54
 *
 * 实现化部分的接口定义(咖啡口味的维度)
 **/
public interface ICoffeeAdditives {

    void addSomething();
}
  • 创建抽象化部分接口定义(咖啡容量维度):
package com.gs.designmodel.bridge.special;

/**
 * @author: Gaos
 * @Date: 2023-08-09 19:55
 *
 * 创建抽象化部分的接口定义(咖啡容量的维度)
 **/
public abstract class Coffee {

    protected ICoffeeAdditives additives;

    public Coffee(ICoffeeAdditives additives) {
        this.additives = additives;
    }

    public abstract void orderCoffee(int count);

}

持有其对象引用,从而将两个对象的行为组合起来。

  • 抽象化修正类(可以增加扩展方法):
package com.gs.designmodel.bridge.special;

import java.util.Random;

/**
 * @author: Gaos
 * @Date: 2023-08-09 19:56
 **/
public abstract class RefinedCoffee extends Coffee{

    public RefinedCoffee(ICoffeeAdditives additives) {
        super(additives);
    }

    public void checkQuality() {
        Random random = new Random();
        System.out.println(String.format("%s 添加%s", additives.getClass().getSimpleName(), random.nextBoolean()? "太多" :"合格"));
    }
}
  • 实现实现化部分(咖啡口味维度)接口 ICoffeeAdditives:
package com.gs.designmodel.bridge.special;

/**
 * @author: Gaos
 * @Date: 2023-08-09 19:59
 *
 * 实现化部分(咖啡口味维度)
 *
 * 加奶
 **/
public class Milk implements ICoffeeAdditives{
    @Override
    public void addSomething() {
        System.out.println("加奶");
    }
}
package com.gs.designmodel.bridge.special;

/**
 * @author: Gaos
 * @Date: 2023-08-09 20:00
 *
 * 实现化部分(咖啡口味维度)
 *
 * 加糖
 **/

public class Sugar implements ICoffeeAdditives{
    @Override
    public void addSomething() {
        System.out.println("加糖");
    }
}
  • 实现抽象化部分(咖啡的容量维度)接口 Coffee:
package com.gs.designmodel.bridge.special;

/**
 * @author: Gaos
 * @Date: 2023-08-09 20:01
 *
 * 实现抽象化部分(咖啡的容量维度)
 * 大杯
 **/
public class LargeCoffee extends RefinedCoffee{

    public LargeCoffee(ICoffeeAdditives additives) {
        super(additives);
    }

    @Override
    public void orderCoffee(int count) {
        additives.addSomething();
        System.out.println("大杯咖啡" + count + "杯");
    }
}
package com.gs.designmodel.bridge.special;

/**
 * @author: Gaos
 * @Date: 2023-08-09 20:02
 *
 *  实现抽象化部分(咖啡的容量维度)
 *  小杯
 *
 **/

public class SmallCoffee extends RefinedCoffee{


    public SmallCoffee(ICoffeeAdditives additives) {
        super(additives);
    }

    @Override
    public void orderCoffee(int count) {
        additives.addSomething();
        System.out.println("小杯咖啡" + count + "杯");
    }
}

........

测试类:

package com.gs.designmodel.bridge.special;

/**
 * @author: Gaos
 * @Date: 2023-08-09 20:03
 **/
public class Test {

    public static void main(String[] args) {
        // 两倍加奶的大杯咖啡
        RefinedCoffee largeCoffee = new LargeCoffee(new Milk());
        largeCoffee.orderCoffee(2);
        largeCoffee.checkQuality();
    }
}

结果:

加奶
大杯咖啡2杯
Milk 添加太多

使用桥接模式,可以使咖啡的容量和口味这两个维度独立变化、互不干扰。原本需要3*3的组合,现在只需要3+3了。

后面如果新增一种口味就会减少的更加明显 3*4-->3+4

4.总结

桥接、状态、策略模式的接口都非常相似。实际上它们都基于组合模式,将工作委派给其他对象,来解决各自不同的问题。

桥接模式可以分离抽象接口及其实现部分。使用了对象之间的关联关系 解耦了抽象和实现之间固有的绑定关系。使它们可以沿着各自的维度来变化,也就是说抽象和实现不在同一个继承层次结构中,而是子类化它们,使他们各自都具有自己的子类,以便组合任何子类,从而获得多维度组合对象。

但是桥接模式难度也比较高,需要对系统和需求有深刻理解,合理的规划正确维度需要一定的经验积累。

相关参考文章:

refactoringguru.cn/design-patt…

blog.csdn.net/ShuSheng000…