桥接模式——独立多维度变化的解决方案

312 阅读4分钟

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

桥接模式(Bridge Pattern)的定义:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

问题场景

现在我们要设计一个数据库导出工具,它可以将数据导出成txt格式,csv格式,xls格式,并且它需要支持不同的数据库,如MySQL、Oracle等。

初步设计:

step1:抽象一个导出接口

public interface Export {
    // 导出数据
    void export();
}

step2:定义抽象类,将数据导出成不同的格式。(之所以定义为抽象类,是因为数据源并没有确定)

// txt格式导出
public abstract class TxtExport implements Export{
    @Override
    public void export() {
        System.out.println("导出txt格式数据");
    }
}
// cvs格式导出
public abstract class CsvExport implements Export {
    @Override
    public void export() {
        System.out.println("导出csv格式数据");
    }
}
// xls格式
public abstract class XlsExport implements Export{
    @Override
    public void export() {
        System.out.println("导出Xls格式数据");
    }
}

step3:具体的数据导出类

// MySQL数据导出txt
public class MySQLTxtExport extends TxtExport{
    @Override
    public void export() {
        System.out.println("获取MySQL数据");
        super.export();
    }
}
// Oracle数据导出txt
public class OracleTxtExport extends TxtExport {
    @Override
    public void export() {
        System.out.println("获取Oracle数据");
        super.export();
    }
}

类似地,如果我们需要实现MySQL和Oracle导出xls功能,就还需创建MySQLXlsExport、OracleXlsExport类。

从上面的实现过程中,我们可以发现几个问题:

  • 从数据库获取数据和导出不同格式数据耦合在一起来了。(MySQLTxtExport类中的export方法)
  • 获取数据和导出不能独立变化,比如获取数据方式需要改,每个相关的实现类都需要同步修改
  • 扩展不方便,如果新增一个导出格式,针对每个数据库都需要扩展一个类。

为了解决以上问题,我们引入桥接模式。

桥接模式的结构

桥接(Bridge)模式包含以下主要角色。

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

类图表示

R-C.png

具体实现

针对上面的问题场景,我们用桥接模式实现它

  • 实例化角色:数据库获取服务接口
public interface Server {
    void getData();
}
  • 抽象化(Abstraction)角色:实现数据导出的抽象化功能,并包含实例化的引用
public abstract class AbstractExport {
    protected Server server;
​
    public void setServer(Server server) {
        this.server = server;
    }
​
    /**
     * 导出
     */
    public abstract void export();
}
  • 扩展抽象化角色:通过组合关系调用实现化角色中的业务方法,实现具体的业务功能
// 数据库数据导出txt格式数据
public class TxtExport extends AbstractExport {
    @Override
    public void export() {
        // 获取数据库数据
        super.server.getData();
        System.out.println("导出Txt格式数据");
    }
}
// 数据库数据导出Csv格式数据
public class CsvExport extends AbstractExport {
    @Override
    public void export() {
        // 获取数据库数据
        super.server.getData();
        System.out.println("导出Csv格式数据");
    }
}
  • 具体实现化角色:从具体数据库获取数据
// 从MySQL数据库获取数据
public class MySQLServer implements Server {
    @Override
    public void getData() {
        System.out.println("获取MySQL数据");
    }
}
// 从Oracle数据库获取数据
public class OracleServer implements Server {
    @Override
    public void getData() {
        System.out.println("获取Oracle数据");
    }
}
  • 客户端使用
public class Client {
    public static void main(String[] args) {
        Server server = new MySQLServer();
        AbstractExport txtExport = new TxtExport();
        txtExport.setServer(server);
        txtExport.export();
    }
}
// 数据结果
获取MySQL数据
导出Txt格式数据

如果想增加其他数据库的导出,我们只需要新增一个实现了Server接口的类,其它地方并不要改动。

public class SQLiteServer implements Server{
    @Override
    public void getData() {
        System.out.println("获取SQLite数据");
    }
}

小结

  • 桥接模式非常适合系统有多个独立的变化维度的场景,它可以降低系统的复杂度
  • 桥接模式提现了很多面向对象的设计原则,包括单一职责原则、开闭原则、合成复用原则、依赖倒置原则等

参考资料

《设计模式的艺术:软件开发人员内功修炼之道》