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个解析类 |
🎯 关键设计决策
-
稳定维度作为抽象层
将数据存储的核心流程(数据验证+存储)封装在抽象层,保证即使解析策略千变万化,存储主流程不会受影响。 -
高频变化维度作为实现层
每个网站的解析策略独立实现,遵循单一职责原则,修改淘宝解析不会影响豆瓣解析。
💡 当解析策略复杂时的进阶方案
如果解析策略需要更灵活的组合(例如:先提取正文再过滤敏感词),可以结合装饰器模式:
// 基础解析器
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("敏感词", "***"); // 替换敏感词
}
}
桥接模式的本质——通过分离变化轴心,让每个维度的变化都收敛在自己的类层次中。
- 解析策略的频繁变化 → 作为实现层接口
- 存储方式的相对稳定 → 作为抽象层基类
- 通过组合获得灵活性 →
DataSaver持有Parser引用
这种设计让系统像拼装乐高积木一样:
- 增加新网站? ➡️ 写个新
Parser实现 - 换存储方式? ➡️ 继承
DataSaver写子类 - 修改数据流程? ➡️ 只需改
DataSaver基类
4. 核心优势
| 场景 | 传统继承方案 | 桥接模式方案 | 优势 |
|---|---|---|---|
| 3种解析方式 × 3种存储方式 | 9个类 | 6个类 | 减少33%代码量 |
| 新增存储方式(如Redis) | 需修改所有解析器类 | 只需新增RedisSaver类 | 符合开闭原则 |
| 运行时切换存储目标 | 无法动态切换 | 直接替换saver成员 | 灵活性更高 |
5. 适用场景
- ✅ 多维度扩展:当系统需要在2+个独立维度上扩展时
(如:UI控件 × 渲染引擎、支付方式 × 支付渠道) - ✅ 避免继承臃肿:当继承导致类层次过深、类数量爆炸时
- ✅ 动态切换行为:需要在运行时切换不同实现策略时
6. 记忆口诀
「抽象握实现,维度各自变;组合替继承,类量不爆炸」
下次遇到多维度组合需求时,先画两个正交的坐标轴,用桥接模式让代码像乐高积木一样自由拼装!