桥接设计模式

177 阅读5分钟

1. 核心目的

解决多维度组合导致的类爆炸问题,通过将**抽象(策略)实现(具体操作)**分离为两个独立的维度,使它们可以独立扩展,最终通过组合(而非继承)实现灵活的功能搭配。


2. 设计方法

               开始
                ↓
     确定系统中存在多个独立变化维度
                ↓
       分析各维度的变化频率
                ↓
选择变化频率低的维度作为抽象层(Abstraction)
                ↓
变化频率高的维度作为实现层(Implementor)
                ↓
 抽象层通过成员变量持有实现层接口引用
                ↓
             完成设计 


3. 爬虫案例详解

代码架构

桥接模式结构

角色对应组件变化频率设计策略
抽象层(Abstraction)数据存储方式较稳定持有解析器引用
实现层(Implementor)网页解析策略高频变化实现解析接口

1. 实现层(易变维度):解析策略

// 解析策略接口(高频变化)
interface Parser {
    String parse(String rawData);
}

// 具体解析策略(每个网站一个实现)
class DoubanParser implements Parser {
    public String parse(String html) {
        return "解析豆瓣数据: " + html.replaceAll("<script.*?</script>", "");
    }
}

class TaobaoParser implements Parser {
    public String parse(String json) {
        return "解析淘宝数据: " + json.replaceAll("[{}]", "");
    }
}

class WeiboParser implements Parser { /* 类似实现 */ }

2. 抽象层(稳定维度):存储方式

// 存储抽象(核心流程稳定)
abstract class DataSaver {
    protected Parser parser;  // 关键:持有易变的解析器

    public DataSaver(Parser parser) {
        this.parser = parser;
    }

    // 稳定的存储流程
    public final void process(String rawData) {
        String cleanData = parser.parse(rawData); // 委托解析
        validate(cleanData);
        save(cleanData);
    }

    private void validate(String data) {
        System.out.println("验证数据有效性...");
    }

    abstract void save(String data); // 具体存储方式由子类实现
}

// 具体存储实现
class DatabaseSaver extends DataSaver {
    public DatabaseSaver(Parser p) { super(p); }

    void save(String data) {
        System.out.println("存储到数据库:" + data);
    }
}

class FileSaver extends DataSaver { /* 类似实现 */ }

3. 客户端动态组合

public class Demo {
    public static void main(String[] args) {
        // 组合1:豆瓣解析 + 数据库存储
        Parser doubanParser = new DoubanParser();
        DataSaver saver1 = new DatabaseSaver(doubanParser);
        saver1.process("<html>豆瓣电影数据...</html>");

        // 组合2:微博解析 + 文件存储
        DataSaver saver2 = new FileSaver(new WeiboParser());
        saver2.process("{weibo: '热搜内容'}");
        
        // 动态切换解析策略
        saver2.parser = new TaobaoParser(); // 运行时更换为淘宝解析
        saver2.process("{product: '手机'}");
    }
}

🌟 优化后的优势

场景传统方案本方案优势
新增网站解析策略需修改所有存储相关类只需新增XXXParser
修改数据存储方式影响所有解析器只需修改对应的XXXSaver子类
运行时切换解析策略需要重新创建爬虫对象直接替换parser成员即可
数据验证流程升级需在所有解析器中重复实现在抽象层DataSaver统一实现

📊 扩展性对比

假设系统需要支持:

  • 10种存储方式
  • 100种网站解析策略
方案总类数新增1种解析策略的成本
传统继承10×100=1000需要写10个存储组合类
桥接模式10+100=110只需新增1个解析类

🎯 关键设计决策

  1. 稳定维度作为抽象层
    将数据存储的核心流程(数据验证+存储)封装在抽象层,保证即使解析策略千变万化,存储主流程不会受影响。

  2. 高频变化维度作为实现层
    每个网站的解析策略独立实现,遵循单一职责原则,修改淘宝解析不会影响豆瓣解析。


💡 当解析策略复杂时的进阶方案

如果解析策略需要更灵活的组合(例如:先提取正文再过滤敏感词),可以结合装饰器模式

// 基础解析器
class BasicParser implements Parser {
    public String parse(String raw) { /* 基础解析逻辑 */ }
}

// 装饰器:增加敏感词过滤
class SensitiveFilterDecorator extends BasicParser {
    private Parser wrapper;
    public SensitiveFilterDecorator(Parser p) { this.wrapper = p; }
    
    public String parse(String raw) {
        String data = wrapper.parse(raw);
        return data.replaceAll("敏感词", "***");
    }
}

// 使用装饰器组合
Parser parser = new SensitiveFilterDecorator(
                   new AdBlockDecorator(
                       new DoubanParser()));

DataSaver saver = new DatabaseSaver(parser);

new SensitiveFilterDecorator(      // 最外层:敏感词过滤
    new AdBlockDecorator(          // 中间层:广告过滤
        new DoubanParser()         // 核心:基础解析器
    )
)

敏感词装饰器类(既继承抽象接口又包含抽象接口)
class AdBlockDecorator implements Parser {
    private Parser wrappedParser;
    
    public AdBlockDecorator(Parser p) { 
        this.wrappedParser = p; 
    }

    public String parse(String raw) {
        String data = wrappedParser.parse(raw); // 先执行基础解析
        return data.replaceAll("广告", "");      // 移除广告内容
    }
}
广告装饰器类
class SensitiveFilterDecorator implements Parser {
    private Parser wrappedParser;
    
    public SensitiveFilterDecorator(Parser p) { 
        this.wrappedParser = p; 
    }

    public String parse(String raw) {
        String data = wrappedParser.parse(raw); // 先执行广告过滤后的数据
        return data.replaceAll("敏感词", "***"); // 替换敏感词
    }
}

桥接模式的本质——通过分离变化轴心,让每个维度的变化都收敛在自己的类层次中

  1. 解析策略的频繁变化 → 作为实现层接口
  2. 存储方式的相对稳定 → 作为抽象层基类
  3. 通过组合获得灵活性 → DataSaver持有Parser引用

这种设计让系统像拼装乐高积木一样:

  • 增加新网站? ➡️ 写个新Parser实现
  • 换存储方式? ➡️ 继承DataSaver写子类
  • 修改数据流程? ➡️ 只需改DataSaver基类

4. 核心优势

场景传统继承方案桥接模式方案优势
3种解析方式 × 3种存储方式9个类6个类减少33%代码量
新增存储方式(如Redis)需修改所有解析器类只需新增RedisSaver符合开闭原则
运行时切换存储目标无法动态切换直接替换saver成员灵活性更高

5. 适用场景

  • 多维度扩展:当系统需要在2+个独立维度上扩展时
    (如:UI控件 × 渲染引擎、支付方式 × 支付渠道)
  • 避免继承臃肿:当继承导致类层次过深、类数量爆炸时
  • 动态切换行为:需要在运行时切换不同实现策略时

6. 记忆口诀

「抽象握实现,维度各自变;组合替继承,类量不爆炸」
下次遇到多维度组合需求时,先画两个正交的坐标轴,用桥接模式让代码像乐高积木一样自由拼装!