桥接模式在项目中的应用

1,828 阅读6分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

桥接模式也是23种设计模式中比较常用的模式之一,在创建型模式、结构性模式和行为型模式分类中,桥接模式归类为结构型模式。整个桥接模式的核心就是将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度

应用场景

桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。 因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。

FE90BAE0-E666-4A54-9800-B0993B2EE816

现实生活中我们看见桥接这两个字可能在路由器配置时候看见叫路由桥接。其实概念比较相似,路由器的桥接其实就是2个路由器把其中一个路由器接入到另外一个路由器下面。这两个路由器都可以对外提供wifi信号,终端(手机,电脑)相当于子类都可以用这两个路由器发出的wifi信号。代码中的桥接就是两个抽象接口其中一个抽象接口接入到另一个抽象接口中,二者都有对应的实现类,这样就可以编程笛卡尔积组合,进而增强实用性和扩展性。

我认为如果程序设计上出现笛卡尔积的情况时就要考虑采用该设计模式了

代码示例

我们列举一个实际的场景来用代码实现一下,实际感受一下实际应用的场景,我们买奶茶的时候可供选择有口味,容量也就是大杯小杯。比如有热奶茶和冰奶茶,对应的有大杯和小杯。这样就出现了笛卡尔积的情况组合。

  • 热的大杯奶茶
  • 热的小杯奶茶
  • 凉的大杯奶茶
  • 凉的小杯奶茶

我们先用if-else来实现一下。

public class MilkTeaController {
    private Logger logger = LoggerFactory.getLogger(MilkTeaController.class);

    /**
     * 来杯奶茶
     *
     * @param type     1热的   2凉的
     * @param modeType 1大杯   2小杯
     * @return
     */
    public boolean getMilkTea(int type, int modeType) {
        if (1 == type) {
            //热奶茶
            if (1 == modeType) {
                logger.info("来个热的大杯奶茶");
            } else if (2 == modeType) {
                logger.info("来个热的小杯奶茶");
            }
        }
        if (2 == type) {
            //凉奶茶
            if (1 == modeType) {
                logger.info("来个凉的大杯奶茶");
            } else if (2 == modeType) {
                logger.info("来个凉的小杯奶茶");
            }
        }
        return true;

    }
}

如上代码这么写的绝对不在少数。如果像上面这样写能不能实现正常业务,绝对没问题,但是如果要进行扩展就会写更多的if-else。到后来写着写着就没办法维护了。有人提出我们是面向接口编程我们可以进行抽象一层出来就没有这么多if-else了。这样写:

  1. 抽象一个奶茶类
public interface IMilkTea {
    //点个奶茶
    void orderMilkTea(int modeType);
}
  1. 热奶茶实现
public class HotMilkTea implements IMilkTea {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void orderMilkTea(int modeType) {
        if (1 == modeType) {
            logger.info("来个热的大杯奶茶");
        } else if (2 == modeType) {
            logger.info("来个热的小杯奶茶");
        }
    }
}
  1. 凉奶茶实现
public class ColdMilkTea implements IMilkTea {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void orderMilkTea(int modeType) {
        if (1 == modeType) {
            logger.info("来个凉的大杯奶茶");
        } else if (2 == modeType) {
            logger.info("来个凉的小杯奶茶");
        }
    }
}

这样写,我只能说:嗯,有内味了。但是改造的不彻底。我们用桥接模式改造一下看看有哪些便利:

  1. 我们做第一个抽象,抽象一个热或者凉的概念,代码如下:
public abstract class HotOrCold {

    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    public BigOrSmall bigOrSmall;

    public HotOrCold(BigOrSmall bigOrSmall) {
        this.bigOrSmall = bigOrSmall;
    }
    //订单制作
    public abstract void OperationOrder();

}

上面的这个抽象类中介入了一个接口对象。这个就是整个桥接的核心,也就是桥接的"桥"的概念。

  1. 我们接着做第二个抽象。将大小杯进行抽象,代码:
public interface BigOrSmall {
    int selectMode();
}
  1. 接下来我们对具体抽象进行实现
public class ColdMilkTea2 extends HotOrCold {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public ColdMilkTea2(BigOrSmall bigOrSmall) {
        super(bigOrSmall);
    }

    @Override
    public void OperationOrder() {
        System.out.print("凉奶茶-");
        bigOrSmall.selectMode();
    }
}
public class HotMilkTea2 extends HotOrCold {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public HotMilkTea2(BigOrSmall bigOrSmall) {
        super(bigOrSmall);
    }

    @Override
    public void OperationOrder() {
        System.out.print("热奶茶-");
        bigOrSmall.selectMode();
    }
}

public class BigCup implements BigOrSmall {
    @Override
    public int selectMode() {
        System.out.println("大杯");
        return 1;
    }
}
public class SmallCup implements BigOrSmall {
    @Override
    public int selectMode() {
        System.out.println("小杯");
        return 2;
    }
}

好了,这样我们就定义好了,我们用个客户端测试一下:

public class Client {
    public static void main(String[] args) {

        HotOrCold milkOrder = new HotMilkTea2(new BigCup());
        milkOrder.OperationOrder();
        System.out.println("--------------------------");
        HotOrCold milkOrder2 = new HotMilkTea2(new SmallCup());
        milkOrder2.OperationOrder();
        System.out.println("--------------------------");
        HotOrCold milkOrder3 = new ColdMilkTea2(new BigCup());
        milkOrder3.OperationOrder();
        System.out.println("--------------------------");
        HotOrCold milkOrder4 = new ColdMilkTea2(new SmallCup());
        milkOrder4.OperationOrder();

    }
}

输出结果

热奶茶-大杯 热奶茶-小杯 凉奶茶-大杯 凉奶茶-小杯

这样如果以后推出中杯奶茶,那我们无需改动原有逻辑只需要增加一个类:

public class MediumCup implements BigOrSmall {
    @Override
    public int selectMode() {
        System.out.println("中杯");
        return 3;
    }
}

就能很好的满足需求了。

模式中角色

结合上面我们的例子我们总结一下在桥接模式中各个角色的组成

角色含义
抽象化(Abstraction)角色定义抽象类,并包含一个对实现化对象的引用
扩展抽象化(Refined Abstraction)角色是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
实现化(Implementor)角色定义实现化角色的接口,供扩展抽象化角色调用
具体实现化(Concrete Implementor)角色给出实现化角色接口的具体实现

桥接模式优缺点

优点
  • 解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
缺点
  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

桥接模式在框架中的应用

我们经常使用的JDBC连接数据库中的Driver类就使用了变形的桥接模式。我们先看一下Driver接口类: aHR0cHM6Ly9naXRlZS5jb20venl4c2N1ZWMvaW1hZ2UvcmF3L21hc3Rlci8vaW1nLzIwMjAwODAyMTc1MTQzLnBuZw

这是一个接口,下面具体的实现可以有mysql的driver或者sqlsever的driver。我们拿mysql的driver举个例子:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

调用了DriverManager中的registerDriver方法来注册驱动。当驱动注册完成后,我们就会开始调用DriverManager中的getConnection方法了。我们看一下这个geConnection方法: 2

可以看到需要返回的是Connection对象。在Java中通过Connection提供给各个数据库一样的操作接口,这里的Connection可以看作抽象类。可以说我们用来操作不同数据库的方法都是相同的,不过MySQL有自己的ConnectionImpl类,同样Oracle也有对应的实现类。这里Driver和Connection之间是通过DriverManager类进行桥接的,不是像我们上面说的那样用组合关系来进行桥接。