[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-07":26,"\u002Farticles\u002Fdesign-pattern-07\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-07\u002Fadjacent":70,"\u002Fcategories":82,"render-design-pattern-07":108},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113931455091445761","design-pattern-07","设计模式修炼之路 ⑦ 桥接模式——m×n 降为 m+n","继承爆炸的解药、m×n 降为 m+n、在 JDBC 里找它","2113897628365230081",1,1907,5226,14,"2023-12-15T15:12:08","上一篇适配器模式治好了\"接口对不上\"，结尾留了个尾巴：当**两个维度一起膨胀**——形状要乘以颜色、品牌要乘以型号——继承图开始爆炸时，该请谁出场？答案就是结构型模式的第二位选手：**桥接模式（Bridge Pattern）**。适配器是事后买转接头，桥接是事前就想明白：\"这事儿明明有两个会变的维度，为什么非用继承焊死在一起？\"\n\n## 一、什么是桥接模式？\n\n先看定义：\n\n> 将抽象部分与它的实现部分分离，使它们都可以独立地变化。\n\n翻译成人话：**当\"做什么\"和\"怎么做\"是两个独立变化的维度时，别用继承硬扛，把一个维度组合进另一个维度，让两边各自演化**。\n\n生活类比就用画图这件事本身：画图天生有两个维度——**画什么**（圆、方、三角……）和**用什么颜色画**（红、绿、蓝……）。没有人会为\"红圆、绿方、蓝三角\"每种组合专门备一支画笔——圆规、三角板是工具维度，红颜料、绿颜料是颜料维度，分开备货，用时蘸一下就组合。桥接模式干的就是这件事：把两个维度拆成**两棵独立的继承树**，再用组合把它们连起来。\n\n桥接模式有四个角色：\n\n- **Abstraction（抽象化）**：形状维度的顶层。它知道\"画什么\"，但不亲自动手，把\"怎么上色\"委托给手里的实现者；\n- **RefinedAbstraction（扩充抽象化）**：Abstraction 的子类，Circle、Square——形状维度上的具体成员；\n- **Implementor（实现者接口）**：颜色维度的接口，DrawAPI。注意它**不实现** Abstraction，它是另一棵独立的树；\n- **ConcreteImplementor（具体实现者）**：RedDraw、GreenDraw——颜色维度上的具体成员。\n\n最容易犯的迷糊在这里：定义里的\"抽象\"和\"实现\"，**不是**\"abstract 父类和它的子类\"那种关系，而是**两棵平行的树**：一棵管\"做什么\"（形状），一棵管\"怎么做\"（上色）。Abstraction 持有 Implementor 的引用，这条跨树的组合关系，就是那座\"桥\"——名字由此而来。\n\n## 二、桥接模式的优缺点\n\n### 优点\n\n1. **两个维度独立扩展，互不牵连**。加三角形只动形状树，加紫色只动颜色树，谁也不碰谁——完美符合开闭原则。\n2. **类数量从 m×n 降到 m+n**。5 种形状 × 8 种颜色，继承写法要 40 个组合类，桥接只要 13 个（5 + 8）。维度越多、成员越多，这笔账越吓人。\n3. **运行时可切换实现**。构造函数传哪支\"颜料\"，图形就长什么色，同一棵形状树配任何颜色树都行。\n4. **细节对客户端透明**。客户端只面对两棵树的抽象层做组合，不用关心具体类怎么配对。\n\n### 缺点\n\n1. **得先看穿\"有两个独立维度\"**。桥接是预防针，得在设计期就识别出变化维度；要是等继承图炸了才重构，成本不小。\n2. **增加理解成本**。\"Shape 里怎么攥着个 DrawAPI\"——一条跨树引用不如一棵继承树直观，新人第一眼容易懵。\n3. **维度识别错了全白搭**。如果两个维度其实强耦合（比如颜色离开具体形状毫无意义），硬拆反而把简单问题搞复杂。\n\n## 三、桥接模式的应用场景\n\n桥接模式在 JDK 和主流框架里都有教科书级的案例：\n\n- **JDBC**：最经典的一例。你调 `DriverManager.getConnection()` 拿到的是 `java.sql.Connection` 接口（抽象），底下真干活的，是 MySQL \u002F Oracle \u002F PostgreSQL 各家驱动包里的实现类（实现）。换数据库，改改 JDBC URL 和驱动依赖，业务代码一行不动——\"SQL API\"和\"具体驱动\"两个维度各自变化、各自升级，互不拖累。\n- **AWT**：`java.awt.Component`（按钮、文本框，抽象树）和 `ComponentPeer`（Windows \u002F X Window \u002F Mac 的本地实现，实现树）。按钮不关心自己跑在哪个操作系统上，渲染的事全权委托给 Peer——Java GUI \"一次编写到处运行\"，桥接立了大功。\n- **日常业务里**：支付场景——支付方式（微信\u002F支付宝\u002F银联）× 支付渠道（APP\u002FH5\u002F小程序），用继承写就是 WxAppPay、WxH5Pay、AliAppPay……8 个类起步，每加一个维度成员爆炸一轮；桥接拆成\"方式树 + 渠道树\"立刻清爽。消息推送同理：消息类型 × 发送通道（短信\u002F邮件\u002F站内信\u002F钉钉）。\n\n**什么时候该用**：需求里一眼能看出两个**天生独立、且都会持续增加成员**的维度（形状×颜色、方式×渠道、品牌×型号），组合又自由多变——提前用桥接拆树，而不是等继承图爆炸了再补。\n\n## 四、桥接模式的实现方式\n\n示例代码在 design-pattern 项目的 bridge 包里：\n\n```\ndesign-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── bridge                       \u002F\u002F 桥接模式\n    ├── DrawAPI.java             \u002F\u002F 实现者接口：颜色维度\n    ├── RedDraw.java             \u002F\u002F 具体实现者：红\n    ├── GreenDraw.java           \u002F\u002F 具体实现者：绿\n    ├── BlueDraw.java            \u002F\u002F 具体实现者：蓝（演示\"新增只加一个类\"）\n    ├── Shape.java               \u002F\u002F 抽象化：形状维度，持有桥\n    ├── Circle.java              \u002F\u002F 扩充抽象化：圆\n    ├── Square.java              \u002F\u002F 扩充抽象化：方形\n    ├── BridgeTest.java          \u002F\u002F 测试入口\n    └── explosion                \u002F\u002F 反面教材：继承爆炸\n        ├── RedCircle.java\n        ├── GreenCircle.java\n        ├── RedSquare.java\n        └── GreenSquare.java\n```\n\n场景：画图软件要支持多种形状 × 多种颜色。先看用继承硬扛的下场，再看桥接怎么救。\n\n### 1. 反面教材：继承爆炸\n\n最直觉的写法：形状当父类，颜色当子类分支——每种\"形状×颜色\"组合一个类：\n\n```java\npublic class RedCircle {\n\n    public void draw() {\n        System.out.println(\"用 RedCircle 类画了一个红色的圆形\");\n    }\n}\n```\n\nGreenCircle、RedSquare、GreenSquare 全部照抄，只是类名和打印文案不同。2×2=4 个类，目前还忍得了，跑起来是这样：\n\n```\n===== 反面教材：继承爆炸 =====\n用 RedCircle 类画了一个红色的圆形\n用 GreenCircle 类画了一个绿色的圆形\n用 RedSquare 类画了一个红色的方形\n用 GreenSquare 类画了一个绿色的方形\n```\n\n但数学账不骗人：加一个**三角形**，4 变 6；再加一个**蓝色**，6 变 9；真实系统里 10 种形状 × 8 种颜色 = **80 个类**，每个类都在复制粘贴大同小异的代码。更要命的是，新增任何一个维度的成员，都要横跨整个继承图补一排类——\"一处新增，处处开工\"，开闭原则碎了一地。\n\n### 2. 桥接版：两棵树 + 一条组合线\n\n换个思路：形状和颜色明明是两个维度，凭什么让颜色\"寄生\"在形状的继承树里？把它请出来，单独站成一棵树。\n\n先立**实现者接口**——颜色维度，只管\"怎么画\"：\n\n```java\n\u002F\u002F 实现者接口：颜色维度，只管\"怎么画\"\npublic interface DrawAPI {\n\n    void draw(String shape);\n}\n```\n\n再来两个**具体实现者**：\n\n```java\n\u002F\u002F 具体实现者：红色画笔\npublic class RedDraw implements DrawAPI {\n\n    @Override\n    public void draw(String shape) {\n        System.out.println(\"用红色画笔\" + shape);\n    }\n}\n```\n\nGreenDraw 照抄换个颜色字。BlueDraw 也一样——注意，它是后来\"新增蓝色\"时**只需要新增的一个类**，形状树一行没动：\n\n```java\n\u002F\u002F 具体实现者：蓝色画笔，新增颜色只加这一个类\npublic class BlueDraw implements DrawAPI {\n\n    @Override\n    public void draw(String shape) {\n        System.out.println(\"用蓝色画笔\" + shape);\n    }\n}\n```\n\n然后是主角，**抽象化**——形状维度，只管\"画什么\"。重点就一行：**持有实现者的引用，这条组合关系就是桥**：\n\n```java\n\u002F\u002F 抽象化：形状维度，只管\"画什么\"\npublic abstract class Shape {\n\n    protected final DrawAPI drawAPI; \u002F\u002F 抽象化持有实现者的引用，这条组合关系就是桥\n\n    protected Shape(DrawAPI drawAPI) {\n        this.drawAPI = drawAPI;\n    }\n\n    public abstract void draw();\n}\n```\n\n**扩充抽象化**继承它，画的时候把\"上色\"委托给手里的画笔：\n\n```java\n\u002F\u002F 扩充抽象化：圆形\npublic class Circle extends Shape {\n\n    private final double radius;\n\n    public Circle(DrawAPI drawAPI, double radius) {\n        super(drawAPI);\n        this.radius = radius;\n    }\n\n    @Override\n    public void draw() {\n        drawAPI.draw(\"画了一个半径\" + radius + \"的圆\");\n    }\n}\n```\n\nSquare 只是把\"半径\"换成\"边长\"，其余雷同。最后看测试代码——两个维度像两排积木，自由拼装：\n\n```java\nprivate static void bridge() {\n    DrawAPI red = new RedDraw();\n    DrawAPI green = new GreenDraw();\n    DrawAPI blue = new BlueDraw();\n    Shape redCircle = new Circle(red, 3);\n    Shape greenCircle = new Circle(green, 3);\n    Shape blueCircle = new Circle(blue, 3);\n    Shape redSquare = new Square(red, 2);\n    Shape greenSquare = new Square(green, 2);\n    Shape blueSquare = new Square(blue, 2);\n    redCircle.draw();\n    greenCircle.draw();\n    blueCircle.draw();\n    redSquare.draw();\n    greenSquare.draw();\n    blueSquare.draw();\n}\n```\n\n运行结果：\n\n```\n===== 桥接版：两个维度独立变化 =====\n用红色画笔画了一个半径3.0的圆\n用绿色画笔画了一个半径3.0的圆\n用蓝色画笔画了一个半径3.0的圆\n用红色画笔画了一个边长2.0的方形\n用绿色画笔画了一个边长2.0的方形\n用蓝色画笔画了一个边长2.0的方形\n```\n\n同样覆盖全部组合，账本完全不同：\n\n- 反面教材覆盖 4 种组合用了 **4 个类**；桥接版覆盖 6 种组合，两棵树加起来 **2 + 3 = 5 个类**；\n- 反面教材每加一种颜色，全树横着补一排类；桥接版加蓝色，**只加一个 BlueDraw**，Shape、Circle、Square 一个字没改；\n- 反过来加三角形也一样——新建 Triangle extends Shape 完事，颜色树原地不动。\n\n这正是**组合优于继承**的第二次大显身手。适配器篇说过它会反复出场：对象适配器用组合替换继承换来灵活，这次桥接用组合替换\"多维继承\"换来的是整个维度树的自由。继承表达的是\"是一种\"（圆**是一种**形状），颜色和形状之间可没有\"是一种\"关系，只有\"搭配使用\"关系——用继承表达搭配，从一开始就用错了工具。\n\n## 五、适配器 vs 桥接：同样是\"中间加一层\"\n\n桥接和适配器经常被新人搞混，因为乍看都是\"两个东西之间加一层\"。但仔细对照，差别全是本质性的：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>\u003C\u002Fth>\u003Cth>适配器模式\u003C\u002Fth>\u003Cth>桥接模式\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>解决什么问题\u003C\u002Ftd>\u003Ctd>接口不兼容\u003C\u002Ftd>\u003Ctd>多维度组合膨胀\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>出场时机\u003C\u002Ftd>\u003Ctd>事后补救：系统成型后发现对不上\u003C\u002Ftd>\u003Ctd>事前设计：动工前就识别出变化维度\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>中间层的身份\u003C\u002Ftd>\u003Ctd>转换器：把 A 的接口翻译成 B\u003C\u002Ftd>\u003Ctd>连接线：两棵树之间搭组合关系\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>有几棵树\u003C\u002Ftd>\u003Ctd>一棵（Adaptee 被包进 Adapter）\u003C\u002Ftd>\u003Ctd>两棵（形状树 + 颜色树平行独立）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>扩展方向\u003C\u002Ftd>\u003Ctd>对齐一个既有接口\u003C\u002Ftd>\u003Ctd>两个维度各自加成员\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>类比\u003C\u002Ftd>\u003Ctd>买转接头\u003C\u002Ftd>\u003Ctd>提前分柜备货\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n一句话总结：**适配器改变接口的\"样子\"，桥接不改变任何接口，只是把\"变化\"本身分类安置**。它俩甚至能配合出场——先用桥接把维度拆开，某天某个具体实现者和新体系接口对不上，再拿适配器救个急。\n\n顺带澄清一个名字误会：上一篇提到的 log4j-over-slf4j 常被叫作\"桥接包\"，但看它的结构——把老框架 API 翻译到新门面，干的是适配器的活儿，只是社区叫顺了口。判断模式看结构意图，别看名字。\n\n## 写在最后\n\n回顾这一篇：桥接模式把\"两个一起膨胀的维度\"从一棵继承树拆成两棵独立的树，用一条组合关系当桥，类数量从 m×n 降到 m+n；四个角色里，Abstraction 持有 Implementor 引用的那行代码就是全部精髓；JDBC 的 Connection 与各家驱动、AWT 的 Component 与 Peer，是它在 JDK 里的高光时刻；它和适配器的区别一句话——适配器是事后急救的转接头，桥接是事前预防的分柜设计。\n\n下一篇继续结构型模式，第三位选手，也是一位编外人员：**过滤器模式（Filter Pattern）**。它不在 GoF 23 人名单里，却是业务代码里的常客：把一堆对象按条件筛来筛去，条件还能像积木一样自由拼接——与、或、非随便组合。列表页的多条件筛选、权限系统的规则判断，背后都是它的影子。","\u003Cp>上一篇适配器模式治好了\"接口对不上\"，结尾留了个尾巴：当\u003Cstrong>两个维度一起膨胀\u003C\u002Fstrong>——形状要乘以颜色、品牌要乘以型号——继承图开始爆炸时，该请谁出场？答案就是结构型模式的第二位选手：\u003Cstrong>桥接模式（Bridge Pattern）\u003C\u002Fstrong>。适配器是事后买转接头，桥接是事前就想明白：\"这事儿明明有两个会变的维度，为什么非用继承焊死在一起？\"\u003C\u002Fp>\n\u003Ch2>一、什么是桥接模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>将抽象部分与它的实现部分分离，使它们都可以独立地变化。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>当\"做什么\"和\"怎么做\"是两个独立变化的维度时，别用继承硬扛，把一个维度组合进另一个维度，让两边各自演化\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>生活类比就用画图这件事本身：画图天生有两个维度——\u003Cstrong>画什么\u003C\u002Fstrong>（圆、方、三角……）和\u003Cstrong>用什么颜色画\u003C\u002Fstrong>（红、绿、蓝……）。没有人会为\"红圆、绿方、蓝三角\"每种组合专门备一支画笔——圆规、三角板是工具维度，红颜料、绿颜料是颜料维度，分开备货，用时蘸一下就组合。桥接模式干的就是这件事：把两个维度拆成\u003Cstrong>两棵独立的继承树\u003C\u002Fstrong>，再用组合把它们连起来。\u003C\u002Fp>\n\u003Cp>桥接模式有四个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Abstraction（抽象化）\u003C\u002Fstrong>：形状维度的顶层。它知道\"画什么\"，但不亲自动手，把\"怎么上色\"委托给手里的实现者；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>RefinedAbstraction（扩充抽象化）\u003C\u002Fstrong>：Abstraction 的子类，Circle、Square——形状维度上的具体成员；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Implementor（实现者接口）\u003C\u002Fstrong>：颜色维度的接口，DrawAPI。注意它\u003Cstrong>不实现\u003C\u002Fstrong> Abstraction，它是另一棵独立的树；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcreteImplementor（具体实现者）\u003C\u002Fstrong>：RedDraw、GreenDraw——颜色维度上的具体成员。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>最容易犯的迷糊在这里：定义里的\"抽象\"和\"实现\"，\u003Cstrong>不是\u003C\u002Fstrong>\"abstract 父类和它的子类\"那种关系，而是\u003Cstrong>两棵平行的树\u003C\u002Fstrong>：一棵管\"做什么\"（形状），一棵管\"怎么做\"（上色）。Abstraction 持有 Implementor 的引用，这条跨树的组合关系，就是那座\"桥\"——名字由此而来。\u003C\u002Fp>\n\u003Ch2>二、桥接模式的优缺点\u003C\u002Fh2>\n\u003Ch3>优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>两个维度独立扩展，互不牵连\u003C\u002Fstrong>。加三角形只动形状树，加紫色只动颜色树，谁也不碰谁——完美符合开闭原则。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>类数量从 m×n 降到 m+n\u003C\u002Fstrong>。5 种形状 × 8 种颜色，继承写法要 40 个组合类，桥接只要 13 个（5 + 8）。维度越多、成员越多，这笔账越吓人。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>运行时可切换实现\u003C\u002Fstrong>。构造函数传哪支\"颜料\"，图形就长什么色，同一棵形状树配任何颜色树都行。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>细节对客户端透明\u003C\u002Fstrong>。客户端只面对两棵树的抽象层做组合，不用关心具体类怎么配对。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>得先看穿\"有两个独立维度\"\u003C\u002Fstrong>。桥接是预防针，得在设计期就识别出变化维度；要是等继承图炸了才重构，成本不小。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>增加理解成本\u003C\u002Fstrong>。\"Shape 里怎么攥着个 DrawAPI\"——一条跨树引用不如一棵继承树直观，新人第一眼容易懵。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>维度识别错了全白搭\u003C\u002Fstrong>。如果两个维度其实强耦合（比如颜色离开具体形状毫无意义），硬拆反而把简单问题搞复杂。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、桥接模式的应用场景\u003C\u002Fh2>\n\u003Cp>桥接模式在 JDK 和主流框架里都有教科书级的案例：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDBC\u003C\u002Fstrong>：最经典的一例。你调 \u003Ccode>DriverManager.getConnection()\u003C\u002Fcode> 拿到的是 \u003Ccode>java.sql.Connection\u003C\u002Fcode> 接口（抽象），底下真干活的，是 MySQL \u002F Oracle \u002F PostgreSQL 各家驱动包里的实现类（实现）。换数据库，改改 JDBC URL 和驱动依赖，业务代码一行不动——\"SQL API\"和\"具体驱动\"两个维度各自变化、各自升级，互不拖累。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>AWT\u003C\u002Fstrong>：\u003Ccode>java.awt.Component\u003C\u002Fcode>（按钮、文本框，抽象树）和 \u003Ccode>ComponentPeer\u003C\u002Fcode>（Windows \u002F X Window \u002F Mac 的本地实现，实现树）。按钮不关心自己跑在哪个操作系统上，渲染的事全权委托给 Peer——Java GUI \"一次编写到处运行\"，桥接立了大功。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>日常业务里\u003C\u002Fstrong>：支付场景——支付方式（微信\u002F支付宝\u002F银联）× 支付渠道（APP\u002FH5\u002F小程序），用继承写就是 WxAppPay、WxH5Pay、AliAppPay……8 个类起步，每加一个维度成员爆炸一轮；桥接拆成\"方式树 + 渠道树\"立刻清爽。消息推送同理：消息类型 × 发送通道（短信\u002F邮件\u002F站内信\u002F钉钉）。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：需求里一眼能看出两个\u003Cstrong>天生独立、且都会持续增加成员\u003C\u002Fstrong>的维度（形状×颜色、方式×渠道、品牌×型号），组合又自由多变——提前用桥接拆树，而不是等继承图爆炸了再补。\u003C\u002Fp>\n\u003Ch2>四、桥接模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 bridge 包里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>design-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── bridge                       \u002F\u002F 桥接模式\n    ├── DrawAPI.java             \u002F\u002F 实现者接口：颜色维度\n    ├── RedDraw.java             \u002F\u002F 具体实现者：红\n    ├── GreenDraw.java           \u002F\u002F 具体实现者：绿\n    ├── BlueDraw.java            \u002F\u002F 具体实现者：蓝（演示\"新增只加一个类\"）\n    ├── Shape.java               \u002F\u002F 抽象化：形状维度，持有桥\n    ├── Circle.java              \u002F\u002F 扩充抽象化：圆\n    ├── Square.java              \u002F\u002F 扩充抽象化：方形\n    ├── BridgeTest.java          \u002F\u002F 测试入口\n    └── explosion                \u002F\u002F 反面教材：继承爆炸\n        ├── RedCircle.java\n        ├── GreenCircle.java\n        ├── RedSquare.java\n        └── GreenSquare.java\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：画图软件要支持多种形状 × 多种颜色。先看用继承硬扛的下场，再看桥接怎么救。\u003C\u002Fp>\n\u003Ch3>1. 反面教材：继承爆炸\u003C\u002Fh3>\n\u003Cp>最直觉的写法：形状当父类，颜色当子类分支——每种\"形状×颜色\"组合一个类：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class RedCircle {\n\n    public void draw() {\n        System.out.println(\"用 RedCircle 类画了一个红色的圆形\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>GreenCircle、RedSquare、GreenSquare 全部照抄，只是类名和打印文案不同。2×2=4 个类，目前还忍得了，跑起来是这样：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 反面教材：继承爆炸 =====\n用 RedCircle 类画了一个红色的圆形\n用 GreenCircle 类画了一个绿色的圆形\n用 RedSquare 类画了一个红色的方形\n用 GreenSquare 类画了一个绿色的方形\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>但数学账不骗人：加一个\u003Cstrong>三角形\u003C\u002Fstrong>，4 变 6；再加一个\u003Cstrong>蓝色\u003C\u002Fstrong>，6 变 9；真实系统里 10 种形状 × 8 种颜色 = \u003Cstrong>80 个类\u003C\u002Fstrong>，每个类都在复制粘贴大同小异的代码。更要命的是，新增任何一个维度的成员，都要横跨整个继承图补一排类——\"一处新增，处处开工\"，开闭原则碎了一地。\u003C\u002Fp>\n\u003Ch3>2. 桥接版：两棵树 + 一条组合线\u003C\u002Fh3>\n\u003Cp>换个思路：形状和颜色明明是两个维度，凭什么让颜色\"寄生\"在形状的继承树里？把它请出来，单独站成一棵树。\u003C\u002Fp>\n\u003Cp>先立\u003Cstrong>实现者接口\u003C\u002Fstrong>——颜色维度，只管\"怎么画\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 实现者接口：颜色维度，只管\"怎么画\"\npublic interface DrawAPI {\n\n    void draw(String shape);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>再来两个\u003Cstrong>具体实现者\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 具体实现者：红色画笔\npublic class RedDraw implements DrawAPI {\n\n    @Override\n    public void draw(String shape) {\n        System.out.println(\"用红色画笔\" + shape);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>GreenDraw 照抄换个颜色字。BlueDraw 也一样——注意，它是后来\"新增蓝色\"时\u003Cstrong>只需要新增的一个类\u003C\u002Fstrong>，形状树一行没动：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 具体实现者：蓝色画笔，新增颜色只加这一个类\npublic class BlueDraw implements DrawAPI {\n\n    @Override\n    public void draw(String shape) {\n        System.out.println(\"用蓝色画笔\" + shape);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然后是主角，\u003Cstrong>抽象化\u003C\u002Fstrong>——形状维度，只管\"画什么\"。重点就一行：\u003Cstrong>持有实现者的引用，这条组合关系就是桥\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 抽象化：形状维度，只管\"画什么\"\npublic abstract class Shape {\n\n    protected final DrawAPI drawAPI; \u002F\u002F 抽象化持有实现者的引用，这条组合关系就是桥\n\n    protected Shape(DrawAPI drawAPI) {\n        this.drawAPI = drawAPI;\n    }\n\n    public abstract void draw();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>扩充抽象化\u003C\u002Fstrong>继承它，画的时候把\"上色\"委托给手里的画笔：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 扩充抽象化：圆形\npublic class Circle extends Shape {\n\n    private final double radius;\n\n    public Circle(DrawAPI drawAPI, double radius) {\n        super(drawAPI);\n        this.radius = radius;\n    }\n\n    @Override\n    public void draw() {\n        drawAPI.draw(\"画了一个半径\" + radius + \"的圆\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Square 只是把\"半径\"换成\"边长\"，其余雷同。最后看测试代码——两个维度像两排积木，自由拼装：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void bridge() {\n    DrawAPI red = new RedDraw();\n    DrawAPI green = new GreenDraw();\n    DrawAPI blue = new BlueDraw();\n    Shape redCircle = new Circle(red, 3);\n    Shape greenCircle = new Circle(green, 3);\n    Shape blueCircle = new Circle(blue, 3);\n    Shape redSquare = new Square(red, 2);\n    Shape greenSquare = new Square(green, 2);\n    Shape blueSquare = new Square(blue, 2);\n    redCircle.draw();\n    greenCircle.draw();\n    blueCircle.draw();\n    redSquare.draw();\n    greenSquare.draw();\n    blueSquare.draw();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 桥接版：两个维度独立变化 =====\n用红色画笔画了一个半径3.0的圆\n用绿色画笔画了一个半径3.0的圆\n用蓝色画笔画了一个半径3.0的圆\n用红色画笔画了一个边长2.0的方形\n用绿色画笔画了一个边长2.0的方形\n用蓝色画笔画了一个边长2.0的方形\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同样覆盖全部组合，账本完全不同：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>反面教材覆盖 4 种组合用了 \u003Cstrong>4 个类\u003C\u002Fstrong>；桥接版覆盖 6 种组合，两棵树加起来 \u003Cstrong>2 + 3 = 5 个类\u003C\u002Fstrong>；\u003C\u002Fli>\n \u003Cli>反面教材每加一种颜色，全树横着补一排类；桥接版加蓝色，\u003Cstrong>只加一个 BlueDraw\u003C\u002Fstrong>，Shape、Circle、Square 一个字没改；\u003C\u002Fli>\n \u003Cli>反过来加三角形也一样——新建 Triangle extends Shape 完事，颜色树原地不动。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这正是\u003Cstrong>组合优于继承\u003C\u002Fstrong>的第二次大显身手。适配器篇说过它会反复出场：对象适配器用组合替换继承换来灵活，这次桥接用组合替换\"多维继承\"换来的是整个维度树的自由。继承表达的是\"是一种\"（圆\u003Cstrong>是一种\u003C\u002Fstrong>形状），颜色和形状之间可没有\"是一种\"关系，只有\"搭配使用\"关系——用继承表达搭配，从一开始就用错了工具。\u003C\u002Fp>\n\u003Ch2>五、适配器 vs 桥接：同样是\"中间加一层\"\u003C\u002Fh2>\n\u003Cp>桥接和适配器经常被新人搞混，因为乍看都是\"两个东西之间加一层\"。但仔细对照，差别全是本质性的：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>适配器模式\u003C\u002Fth>\n   \u003Cth>桥接模式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>解决什么问题\u003C\u002Ftd>\n   \u003Ctd>接口不兼容\u003C\u002Ftd>\n   \u003Ctd>多维度组合膨胀\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>出场时机\u003C\u002Ftd>\n   \u003Ctd>事后补救：系统成型后发现对不上\u003C\u002Ftd>\n   \u003Ctd>事前设计：动工前就识别出变化维度\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>中间层的身份\u003C\u002Ftd>\n   \u003Ctd>转换器：把 A 的接口翻译成 B\u003C\u002Ftd>\n   \u003Ctd>连接线：两棵树之间搭组合关系\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>有几棵树\u003C\u002Ftd>\n   \u003Ctd>一棵（Adaptee 被包进 Adapter）\u003C\u002Ftd>\n   \u003Ctd>两棵（形状树 + 颜色树平行独立）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>扩展方向\u003C\u002Ftd>\n   \u003Ctd>对齐一个既有接口\u003C\u002Ftd>\n   \u003Ctd>两个维度各自加成员\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>类比\u003C\u002Ftd>\n   \u003Ctd>买转接头\u003C\u002Ftd>\n   \u003Ctd>提前分柜备货\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>一句话总结：\u003Cstrong>适配器改变接口的\"样子\"，桥接不改变任何接口，只是把\"变化\"本身分类安置\u003C\u002Fstrong>。它俩甚至能配合出场——先用桥接把维度拆开，某天某个具体实现者和新体系接口对不上，再拿适配器救个急。\u003C\u002Fp>\n\u003Cp>顺带澄清一个名字误会：上一篇提到的 log4j-over-slf4j 常被叫作\"桥接包\"，但看它的结构——把老框架 API 翻译到新门面，干的是适配器的活儿，只是社区叫顺了口。判断模式看结构意图，别看名字。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：桥接模式把\"两个一起膨胀的维度\"从一棵继承树拆成两棵独立的树，用一条组合关系当桥，类数量从 m×n 降到 m+n；四个角色里，Abstraction 持有 Implementor 引用的那行代码就是全部精髓；JDBC 的 Connection 与各家驱动、AWT 的 Component 与 Peer，是它在 JDK 里的高光时刻；它和适配器的区别一句话——适配器是事后急救的转接头，桥接是事前预防的分柜设计。\u003C\u002Fp>\n\u003Cp>下一篇继续结构型模式，第三位选手，也是一位编外人员：\u003Cstrong>过滤器模式（Filter Pattern）\u003C\u002Fstrong>。它不在 GoF 23 人名单里，却是业务代码里的常客：把一堆对象按条件筛来筛去，条件还能像积木一样自由拼接——与、或、非随便组合。列表页的多条件筛选、权限系统的规则判断，背后都是它的影子。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,53,62],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":36,"publishedAt":52},"2113931305484816386","design-pattern-06","设计模式修炼之路 ⑥ 适配器模式——让不兼容的接口握手","类适配器 vs 对象适配器、在 Spring MVC 里找它",1578,5381,"2023-12-11T21:59:29",{"id":54,"slug":55,"title":56,"summary":57,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":58,"likeCount":4,"wordCount":59,"readingMinutes":60,"publishedAt":61},"2113931965261418497","design-pattern-15","设计模式修炼之路 ⑮ 策略模式——if-else 的解药","一个分支一个类、工厂加 Map 连选择处都不写 if-else、Spring 自动收集、线程池拒绝策略",1681,7953,20,"2024-06-16T18:10:44",{"id":63,"slug":64,"title":65,"summary":66,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":67,"likeCount":4,"wordCount":68,"readingMinutes":60,"publishedAt":69},"2113933638763548673","design-pattern-11","设计模式修炼之路 ⑪ 外观模式——给杂乱子系统一个入口","给杂乱子系统一个统一入口、只编排不干活、SLF4J 名字里的 Facade",3176,7681,"2024-03-20T15:49:38",{"code":4,"message":5,"data":71},{"prev":72,"next":73},{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":36,"publishedAt":52},{"id":74,"slug":75,"title":76,"summary":77,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":78,"likeCount":4,"wordCount":79,"readingMinutes":80,"publishedAt":81},"2113931511261564929","design-pattern-08","设计模式修炼之路 ⑧ 过滤器模式——与或非条件当积木","非 GoF、与或非条件当积木、Predicate 是 JDK 收编版",3801,7442,19,"2024-01-08T20:51:45",{"code":4,"message":5,"data":83},[84,90,95,101],{"id":85,"name":86,"slug":87,"description":88,"sortOrder":4,"articleCount":89},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":91,"slug":92,"description":93,"sortOrder":4,"articleCount":94},"设计模式","design-pattern","设计模式系列","34",{"id":96,"name":97,"slug":98,"description":99,"sortOrder":33,"articleCount":100},"1946000000000000002","随笔","essay","生活与思考","0",{"id":102,"name":103,"slug":104,"description":105,"sortOrder":106,"articleCount":107},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":109,"toc":110},"\u003Cp>上一篇适配器模式治好了\"接口对不上\"，结尾留了个尾巴：当\u003Cstrong>两个维度一起膨胀\u003C\u002Fstrong>——形状要乘以颜色、品牌要乘以型号——继承图开始爆炸时，该请谁出场？答案就是结构型模式的第二位选手：\u003Cstrong>桥接模式（Bridge Pattern）\u003C\u002Fstrong>。适配器是事后买转接头，桥接是事前就想明白：\"这事儿明明有两个会变的维度，为什么非用继承焊死在一起？\"\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、什么是桥接模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>将抽象部分与它的实现部分分离，使它们都可以独立地变化。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>当\"做什么\"和\"怎么做\"是两个独立变化的维度时，别用继承硬扛，把一个维度组合进另一个维度，让两边各自演化\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>生活类比就用画图这件事本身：画图天生有两个维度——\u003Cstrong>画什么\u003C\u002Fstrong>（圆、方、三角……）和\u003Cstrong>用什么颜色画\u003C\u002Fstrong>（红、绿、蓝……）。没有人会为\"红圆、绿方、蓝三角\"每种组合专门备一支画笔——圆规、三角板是工具维度，红颜料、绿颜料是颜料维度，分开备货，用时蘸一下就组合。桥接模式干的就是这件事：把两个维度拆成\u003Cstrong>两棵独立的继承树\u003C\u002Fstrong>，再用组合把它们连起来。\u003C\u002Fp>\n\u003Cp>桥接模式有四个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Abstraction（抽象化）\u003C\u002Fstrong>：形状维度的顶层。它知道\"画什么\"，但不亲自动手，把\"怎么上色\"委托给手里的实现者；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>RefinedAbstraction（扩充抽象化）\u003C\u002Fstrong>：Abstraction 的子类，Circle、Square——形状维度上的具体成员；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Implementor（实现者接口）\u003C\u002Fstrong>：颜色维度的接口，DrawAPI。注意它\u003Cstrong>不实现\u003C\u002Fstrong> Abstraction，它是另一棵独立的树；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcreteImplementor（具体实现者）\u003C\u002Fstrong>：RedDraw、GreenDraw——颜色维度上的具体成员。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>最容易犯的迷糊在这里：定义里的\"抽象\"和\"实现\"，\u003Cstrong>不是\u003C\u002Fstrong>\"abstract 父类和它的子类\"那种关系，而是\u003Cstrong>两棵平行的树\u003C\u002Fstrong>：一棵管\"做什么\"（形状），一棵管\"怎么做\"（上色）。Abstraction 持有 Implementor 的引用，这条跨树的组合关系，就是那座\"桥\"——名字由此而来。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、桥接模式的优缺点\u003C\u002Fh2>\n\u003Ch3 id=\"toc-3\">优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>两个维度独立扩展，互不牵连\u003C\u002Fstrong>。加三角形只动形状树，加紫色只动颜色树，谁也不碰谁——完美符合开闭原则。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>类数量从 m×n 降到 m+n\u003C\u002Fstrong>。5 种形状 × 8 种颜色，继承写法要 40 个组合类，桥接只要 13 个（5 + 8）。维度越多、成员越多，这笔账越吓人。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>运行时可切换实现\u003C\u002Fstrong>。构造函数传哪支\"颜料\"，图形就长什么色，同一棵形状树配任何颜色树都行。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>细节对客户端透明\u003C\u002Fstrong>。客户端只面对两棵树的抽象层做组合，不用关心具体类怎么配对。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"toc-4\">缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>得先看穿\"有两个独立维度\"\u003C\u002Fstrong>。桥接是预防针，得在设计期就识别出变化维度；要是等继承图炸了才重构，成本不小。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>增加理解成本\u003C\u002Fstrong>。\"Shape 里怎么攥着个 DrawAPI\"——一条跨树引用不如一棵继承树直观，新人第一眼容易懵。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>维度识别错了全白搭\u003C\u002Fstrong>。如果两个维度其实强耦合（比如颜色离开具体形状毫无意义），硬拆反而把简单问题搞复杂。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-5\">三、桥接模式的应用场景\u003C\u002Fh2>\n\u003Cp>桥接模式在 JDK 和主流框架里都有教科书级的案例：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDBC\u003C\u002Fstrong>：最经典的一例。你调 \u003Ccode>DriverManager.getConnection()\u003C\u002Fcode> 拿到的是 \u003Ccode>java.sql.Connection\u003C\u002Fcode> 接口（抽象），底下真干活的，是 MySQL \u002F Oracle \u002F PostgreSQL 各家驱动包里的实现类（实现）。换数据库，改改 JDBC URL 和驱动依赖，业务代码一行不动——\"SQL API\"和\"具体驱动\"两个维度各自变化、各自升级，互不拖累。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>AWT\u003C\u002Fstrong>：\u003Ccode>java.awt.Component\u003C\u002Fcode>（按钮、文本框，抽象树）和 \u003Ccode>ComponentPeer\u003C\u002Fcode>（Windows \u002F X Window \u002F Mac 的本地实现，实现树）。按钮不关心自己跑在哪个操作系统上，渲染的事全权委托给 Peer——Java GUI \"一次编写到处运行\"，桥接立了大功。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>日常业务里\u003C\u002Fstrong>：支付场景——支付方式（微信\u002F支付宝\u002F银联）× 支付渠道（APP\u002FH5\u002F小程序），用继承写就是 WxAppPay、WxH5Pay、AliAppPay……8 个类起步，每加一个维度成员爆炸一轮；桥接拆成\"方式树 + 渠道树\"立刻清爽。消息推送同理：消息类型 × 发送通道（短信\u002F邮件\u002F站内信\u002F钉钉）。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：需求里一眼能看出两个\u003Cstrong>天生独立、且都会持续增加成员\u003C\u002Fstrong>的维度（形状×颜色、方式×渠道、品牌×型号），组合又自由多变——提前用桥接拆树，而不是等继承图爆炸了再补。\u003C\u002Fp>\n\u003Ch2 id=\"toc-6\">四、桥接模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 bridge 包里：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>design-pattern\u002Fsrc\u002Fmain\u002Fjava\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>└── bridge                       \u002F\u002F 桥接模式\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── DrawAPI.java             \u002F\u002F 实现者接口：颜色维度\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── RedDraw.java             \u002F\u002F 具体实现者：红\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── GreenDraw.java           \u002F\u002F 具体实现者：绿\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── BlueDraw.java            \u002F\u002F 具体实现者：蓝（演示\"新增只加一个类\"）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Shape.java               \u002F\u002F 抽象化：形状维度，持有桥\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Circle.java              \u002F\u002F 扩充抽象化：圆\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Square.java              \u002F\u002F 扩充抽象化：方形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── BridgeTest.java          \u002F\u002F 测试入口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    └── explosion                \u002F\u002F 反面教材：继承爆炸\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        ├── RedCircle.java\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        ├── GreenCircle.java\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        ├── RedSquare.java\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        └── GreenSquare.java\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：画图软件要支持多种形状 × 多种颜色。先看用继承硬扛的下场，再看桥接怎么救。\u003C\u002Fp>\n\u003Ch3 id=\"toc-7\">1. 反面教材：继承爆炸\u003C\u002Fh3>\n\u003Cp>最直觉的写法：形状当父类，颜色当子类分支——每种\"形状×颜色\"组合一个类：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RedCircle\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"用 RedCircle 类画了一个红色的圆形\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>GreenCircle、RedSquare、GreenSquare 全部照抄，只是类名和打印文案不同。2×2=4 个类，目前还忍得了，跑起来是这样：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 反面教材：继承爆炸 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用 RedCircle 类画了一个红色的圆形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用 GreenCircle 类画了一个绿色的圆形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用 RedSquare 类画了一个红色的方形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用 GreenSquare 类画了一个绿色的方形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>但数学账不骗人：加一个\u003Cstrong>三角形\u003C\u002Fstrong>，4 变 6；再加一个\u003Cstrong>蓝色\u003C\u002Fstrong>，6 变 9；真实系统里 10 种形状 × 8 种颜色 = \u003Cstrong>80 个类\u003C\u002Fstrong>，每个类都在复制粘贴大同小异的代码。更要命的是，新增任何一个维度的成员，都要横跨整个继承图补一排类——\"一处新增，处处开工\"，开闭原则碎了一地。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">2. 桥接版：两棵树 + 一条组合线\u003C\u002Fh3>\n\u003Cp>换个思路：形状和颜色明明是两个维度，凭什么让颜色\"寄生\"在形状的继承树里？把它请出来，单独站成一棵树。\u003C\u002Fp>\n\u003Cp>先立\u003Cstrong>实现者接口\u003C\u002Fstrong>——颜色维度，只管\"怎么画\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 实现者接口：颜色维度，只管\"怎么画\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DrawAPI\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">shape\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>再来两个\u003Cstrong>具体实现者\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 具体实现者：红色画笔\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RedDraw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DrawAPI\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">shape\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"用红色画笔\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> shape);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>GreenDraw 照抄换个颜色字。BlueDraw 也一样——注意，它是后来\"新增蓝色\"时\u003Cstrong>只需要新增的一个类\u003C\u002Fstrong>，形状树一行没动：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 具体实现者：蓝色画笔，新增颜色只加这一个类\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BlueDraw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DrawAPI\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">shape\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"用蓝色画笔\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> shape);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然后是主角，\u003Cstrong>抽象化\u003C\u002Fstrong>——形状维度，只管\"画什么\"。重点就一行：\u003Cstrong>持有实现者的引用，这条组合关系就是桥\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 抽象化：形状维度，只管\"画什么\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Shape\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    protected\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> DrawAPI drawAPI; \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 抽象化持有实现者的引用，这条组合关系就是桥\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    protected\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Shape\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(DrawAPI \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">drawAPI\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.drawAPI \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> drawAPI;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>扩充抽象化\u003C\u002Fstrong>继承它，画的时候把\"上色\"委托给手里的画笔：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 扩充抽象化：圆形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Circle\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Shape\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> double\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> radius;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Circle\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(DrawAPI \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">drawAPI\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> radius\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        super\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(drawAPI);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.radius \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> radius;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        drawAPI.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"画了一个半径\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> radius \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"的圆\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Square 只是把\"半径\"换成\"边长\"，其余雷同。最后看测试代码——两个维度像两排积木，自由拼装：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> bridge\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    DrawAPI red \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RedDraw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    DrawAPI green \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> GreenDraw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    DrawAPI blue \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BlueDraw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Shape redCircle \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Circle\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(red, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Shape greenCircle \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Circle\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(green, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Shape blueCircle \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Circle\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(blue, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Shape redSquare \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Square\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(red, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Shape greenSquare \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Square\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(green, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Shape blueSquare \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Square\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(blue, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    redCircle.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    greenCircle.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    blueCircle.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    redSquare.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    greenSquare.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    blueSquare.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">draw\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 桥接版：两个维度独立变化 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用红色画笔画了一个半径3.0的圆\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用绿色画笔画了一个半径3.0的圆\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用蓝色画笔画了一个半径3.0的圆\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用红色画笔画了一个边长2.0的方形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用绿色画笔画了一个边长2.0的方形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>用蓝色画笔画了一个边长2.0的方形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同样覆盖全部组合，账本完全不同：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>反面教材覆盖 4 种组合用了 \u003Cstrong>4 个类\u003C\u002Fstrong>；桥接版覆盖 6 种组合，两棵树加起来 \u003Cstrong>2 + 3 = 5 个类\u003C\u002Fstrong>；\u003C\u002Fli>\n \u003Cli>反面教材每加一种颜色，全树横着补一排类；桥接版加蓝色，\u003Cstrong>只加一个 BlueDraw\u003C\u002Fstrong>，Shape、Circle、Square 一个字没改；\u003C\u002Fli>\n \u003Cli>反过来加三角形也一样——新建 Triangle extends Shape 完事，颜色树原地不动。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这正是\u003Cstrong>组合优于继承\u003C\u002Fstrong>的第二次大显身手。适配器篇说过它会反复出场：对象适配器用组合替换继承换来灵活，这次桥接用组合替换\"多维继承\"换来的是整个维度树的自由。继承表达的是\"是一种\"（圆\u003Cstrong>是一种\u003C\u002Fstrong>形状），颜色和形状之间可没有\"是一种\"关系，只有\"搭配使用\"关系——用继承表达搭配，从一开始就用错了工具。\u003C\u002Fp>\n\u003Ch2 id=\"toc-9\">五、适配器 vs 桥接：同样是\"中间加一层\"\u003C\u002Fh2>\n\u003Cp>桥接和适配器经常被新人搞混，因为乍看都是\"两个东西之间加一层\"。但仔细对照，差别全是本质性的：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>适配器模式\u003C\u002Fth>\n   \u003Cth>桥接模式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>解决什么问题\u003C\u002Ftd>\n   \u003Ctd>接口不兼容\u003C\u002Ftd>\n   \u003Ctd>多维度组合膨胀\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>出场时机\u003C\u002Ftd>\n   \u003Ctd>事后补救：系统成型后发现对不上\u003C\u002Ftd>\n   \u003Ctd>事前设计：动工前就识别出变化维度\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>中间层的身份\u003C\u002Ftd>\n   \u003Ctd>转换器：把 A 的接口翻译成 B\u003C\u002Ftd>\n   \u003Ctd>连接线：两棵树之间搭组合关系\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>有几棵树\u003C\u002Ftd>\n   \u003Ctd>一棵（Adaptee 被包进 Adapter）\u003C\u002Ftd>\n   \u003Ctd>两棵（形状树 + 颜色树平行独立）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>扩展方向\u003C\u002Ftd>\n   \u003Ctd>对齐一个既有接口\u003C\u002Ftd>\n   \u003Ctd>两个维度各自加成员\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>类比\u003C\u002Ftd>\n   \u003Ctd>买转接头\u003C\u002Ftd>\n   \u003Ctd>提前分柜备货\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>一句话总结：\u003Cstrong>适配器改变接口的\"样子\"，桥接不改变任何接口，只是把\"变化\"本身分类安置\u003C\u002Fstrong>。它俩甚至能配合出场——先用桥接把维度拆开，某天某个具体实现者和新体系接口对不上，再拿适配器救个急。\u003C\u002Fp>\n\u003Cp>顺带澄清一个名字误会：上一篇提到的 log4j-over-slf4j 常被叫作\"桥接包\"，但看它的结构——把老框架 API 翻译到新门面，干的是适配器的活儿，只是社区叫顺了口。判断模式看结构意图，别看名字。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：桥接模式把\"两个一起膨胀的维度\"从一棵继承树拆成两棵独立的树，用一条组合关系当桥，类数量从 m×n 降到 m+n；四个角色里，Abstraction 持有 Implementor 引用的那行代码就是全部精髓；JDBC 的 Connection 与各家驱动、AWT 的 Component 与 Peer，是它在 JDK 里的高光时刻；它和适配器的区别一句话——适配器是事后急救的转接头，桥接是事前预防的分柜设计。\u003C\u002Fp>\n\u003Cp>下一篇继续结构型模式，第三位选手，也是一位编外人员：\u003Cstrong>过滤器模式（Filter Pattern）\u003C\u002Fstrong>。它不在 GoF 23 人名单里，却是业务代码里的常客：把一堆对象按条件筛来筛去，条件还能像积木一样自由拼接——与、或、非随便组合。列表页的多条件筛选、权限系统的规则判断，背后都是它的影子。\u003C\u002Fp>",[111,114,117,121,124,127,130,133,136,139],{"id":112,"text":113,"level":106},"toc-1","一、什么是桥接模式？",{"id":115,"text":116,"level":106},"toc-2","二、桥接模式的优缺点",{"id":118,"text":119,"level":120},"toc-3","优点",3,{"id":122,"text":123,"level":120},"toc-4","缺点",{"id":125,"text":126,"level":106},"toc-5","三、桥接模式的应用场景",{"id":128,"text":129,"level":106},"toc-6","四、桥接模式的实现方式",{"id":131,"text":132,"level":120},"toc-7","1. 反面教材：继承爆炸",{"id":134,"text":135,"level":120},"toc-8","2. 桥接版：两棵树 + 一条组合线",{"id":137,"text":138,"level":106},"toc-9","五、适配器 vs 桥接：同样是\"中间加一层\"",{"id":140,"text":141,"level":106},"toc-10","写在最后"]