一文搞不懂设计模式之桥接模式

217 阅读6分钟

最近复习设计模式,在大量的单例原型代理工厂适配器装饰器模式中撞死在桥接模式跟前,遂记录下生啃桥接模式的一些轶事。

什么是桥接模式

在能够搜索到的几乎所有教程文章里,桥接模式被如此定义:将抽象部分与实现部分切割分离,使它们可以独立变化。

这个定义的原文是:Decouple an abstraction from its implementation so that the two can vary independently。出自《Design Patterns: Elements of Reusable Object-Oriented Software》。这应该算是官方文档了。

decouple 如果翻译为解耦,上面译文就应该是:解耦抽象与实现,使二者可以独立变化。 听起来更符合程序员の直觉。

什么情况下用桥接

先看官方示例 ↓

前面那个设计模式原著里的示例

在这个图的最上方的两个类里,各自包含有两个不同的扩展:

  • Window : IconWindow 和 TransientWindows
  • WindowImp :XWindowImp 和 PMWindowImp

所以桥接最适用的是【 抽象 和 实现 两个维度都有不同的扩展需求 】的场景。

但老实说前面这个栗子实在让我搞不明白和平常写业务代码时 Service 与 ServiceImp 的区别啊!遂问了DS,依旧得出了大差不差的栗子,但好在 DS 给了总结理解的技巧:

  1. 记住“桥”的比喻:抽象层和实现层时桥的两端,通过组合关系连接
  2. 识别两个变化维度
    1. 业务抽象的变化
    2. 平台实现的变化
  3. 避免混淆
    1. 如果只有一个变化维度,则是普通接口编程
    2. 只有同时存在两个需要独立变化的维度时才是桥接模式

而桥接的最终目的是:使系统可以轻松支持“同一功能的多平台实现”。

因此,我们需要更熟悉、可理解的栗子。

更熟悉的桥接场景

DS 不愧为人机之光(误)立刻给出了最常见的业务侧多维度场景:常规订单(NormalOrder)、预售订单(PreSaleOrder)、拼团订单(GroupBuyOrder)。好家伙本吃谷韭菜看到拼团和预售立刻来精神了。

而平台侧便是常见的不同数据库实现:MySQL、MongoDB、PostgreSQL。

具体的 UML(纯码版)也已经由人机师傅给出。感谢人机师傅。

不同订单业务与不同数据库的桥接UML

在开始对更熟悉的业务场景进行桥接前,先对照着记一下官方给出的关键词和图示:

1.png

桥接模式下有几个关键词:

  • Abstraction :定义抽象的接口(defines the abstraction's interface),维护对 Implementor 类型对象的引用(maintains a reference to an object of type Implementor.)
  • RefineAbstraction :扩展 Abstraction 定义的接口(Extends the interface defined by Abstraction.)
  • Implementor :定义实现类的接口。这个接口不一定完全对应于 Abstraction 的接口;事实上,这两个接口可能非常不同。通常,Implementor 接口只提供基本操作,而 Abstraction 则基于这些基本操作定义更高级别的操作。(defines the interface for implementation classes. This interface doesn't have to correspond exactly to Abstraction's interface; in fact the two interfaces can be quite different. Typically the Implementor interface provides only primitive operations, and Abstraction defines higher-level operations based on these primitives.)
  • ConcreteImplemention :实现 Implementor 接口并定义其具体实现(implements the Implementor interface and defines its concrete implementation.)

在图例中这几种类型彼此间的特定对应关系也用菱形与三角等标记作了说明:

  • Abstraction -> Implementor :依赖
  • Abstraction <- Implementor :聚合
  • Abstraction -◁- RefinedAbstraction :泛化
  • Implementor -◁- ConcreteImplemention :泛化

到这里,就可以来看具体的代码了

Implementor

// Implementor 数据库实现层接口 
public interface DatabaseImplementor {
    void connect(String connStr);
    void insert(String table, Map<String, Object> data);
    List<Map<String, Object>> query(String condition);
    void close();
}

ConcreteImplementor

// 具体的 MySQL 数据库实现
public class MySqlImplementor implements DatabaseImplementor {
    private Connection connection;

    @Override
    public void connect(String connectionString) {
        try {
            System.out.println("Connecting to MySQL: " + connectionString);
            this.connection = DriverManager.getConnection(connectionString);
        } catch (SQLException e) {
            throw new RuntimeException("MySQL connection failed", e);
        }
    }

    @Override
    public void insert(String table, Map<String, Object> data) {
        try (Statement stmt = connection.createStatement()) {
            String columns = String.join(",", data.keySet());
            String values = data.values().stream().map(v -> "'" + v.toString() + "'").collect(Collectors.joining(","));
            String sql = String.format("INSERT INTO %s (%s) VALUES (%s)", table, columns, values);
            stmt.executeUpdate(sql);
        } catch (SQLException e) {
            throw new RuntimeException("MySQL insert failed", e);
        }
    }

    @Override
    public List<Map<String, Object>> query(String table, String condition) {
        List<Map<String, Object>> results = new ArrayList<>();
        try (Statement stmt = connection.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM " + table + " WHERE " + condition)) {
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (rs.next()) {
                Map<String, Object> row = new HashMap<>();
                for (int i = 1; i <= columnCount; i++) {
                    row.put(metaData.getColumnName(i), rs.getObject(i));
                }
                results.add(row);
            }
        } catch (SQLException e) {
            throw new RuntimeException("MySQL query failed", e);
        }
        return results;
    }

    @Override
    public void close() {
        try {
            if (connection != null) {
                connection.close();
                System.out.println("MySQL connection closed");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Abstraction

// Abstraction 订单服务抽象基类。第一个变化维度:业务抽象
public abstract class OrderServiceAbstraction {
    protected DatabaseImplementor implementor; // 第二个变化维度:数据库实现
    
    protected OrderServiceAbstraction(DatabaseImplementor implementor) {
        this.implementor = implementor;
    }
    
    // 公共方法
    public void saveToDatabase(Map<String, Object> data) {
        implementor.insert("orders", data);
    }
    
    public List<Map<String, Object>> queryOrders(String condition) {
        return implementor.query("orders", condition);
    }
    
    // 抽象业务方法
    public abstract void createOrder(Order order);
    public abstract void cancelOrder(String orderId);
    public abstract void payOrder(String orderId);
}

RefinedAbstraction

// RefinedAbstraction 之一,其他两种订单为 PreSaleOrderService 和 GroupBuyOrderService ,相似代码就不重复放了
public class NormalOrderService extends OrderServiceAbstraction {
    public NormalOrderService(DatabaseImplementor implementor) {
        super(implementor);
    }
    
    @Override
    public void createOrder(Order order) {
        // 普通订单创建逻辑
        Map<String, Object> data = new HashMap<>();
        data.put("id", order.getId());
        data.put("type", "normal");
        data.put("status", "created");
        saveToDatabase(data);
        System.out.println("普通订单创建完成");
    }
    
    @Override
    public void cancelOrder(String orderId) {
        // 普通订单可直接取消
        System.out.println("普通订单取消逻辑");
    }
    
    @Override
    public void payOrder(String orderId) {
        // 普通订单支付逻辑
        System.out.println("普通订单支付完成");
    }
}

最终使用

这里给出了在常见的springMVC框架下的调用与在单独的main方法中的使用示例。

spring 中的使用

@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private OrderServiceAbstraction normalOrderService;
    
    @Autowired
    private OrderServiceAbstraction preSaleOrderService;
    
    @PostMapping("/normal")
    public void createNormalOrder(@RequestBody Order order) {
        normalOrderService.createOrder(order);
    }
    
    @PostMapping("/presale")
    public void createPreSaleOrder(@RequestBody Order order) {
        preSaleOrderService.createOrder(order);
    }
}

main 方法的形式

public class OrderClient {
    public static void main(String[] args) {
        // 创建数据库实现(第二个维度)
        DatabaseImplementor mysqlImpl = new MySqlImplementor();
        DatabaseImplementor mongoImpl = new MongoDbImplementor();
        
        // 创建业务抽象(第一个维度)
        OrderServiceAbstraction normalWithMysql = new NormalOrderService(mysqlImpl);
        OrderServiceAbstraction preSaleWithMongo = new PreSaleOrderService(mongoImpl);
        OrderServiceAbstraction groupBuyWithMysql = new GroupBuyOrderService(mysqlImpl);
        
        // 使用组合
        Order order1 = new Order("1001");
        normalWithMysql.createOrder(order1);  // 输出:普通订单创建完成(MySQL)
        
        Order order2 = new Order("1002");
        preSaleWithMongo.createOrder(order2); // 输出:预售订单创建,等待支付定金(MongoDB)
        
        Order order3 = new Order("1003");
        groupBuyWithMysql.createOrder(order3); // 输出:拼团订单创建,等待成团(MySQL)
    }
}

至此,桥接模式的来龙去脉算是彻底捋清楚了。

总结

栗子还是要搞熟悉的才好吃(。

其实理解起来还算简单,前提是理解并掌握了那一大堆单词,以及那个不常使用(比如我就确实不经常用)的 “ 抽象类 ” 的核心本质。

抽象类:通过部分实现和部分抽象的定义,为代码提供了更高层次的灵活性。

特性说明
不能被实例化只能被继承,不能直接new AbstractClass()
可包含抽象方法abstract声明的方法,无实现体,强制子类实现
可包含具体方法可以有已实现的方法,子类可直接继承或重写
可定义成员变量可以包含实例变量、静态变量,子类继承这些字段
构造方法虽然不能实例化,但可以有构造方法供子类调用
多继承限制Java等语言中,类只能单继承,但可实现多个接口

在关联关系上,抽象类 Abstract class 与接口 interface 成为了桥接模式中握手的两方,彼此不关心对方的具体实现,何尝不是一种套人设的换头文学代名词(不对