[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-06":26,"\u002Farticles\u002Fdesign-pattern-06\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-06\u002Fadjacent":71,"\u002Fcategories":90,"render-design-pattern-06":116},{"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},"2113931305484816386","design-pattern-06","设计模式修炼之路 ⑥ 适配器模式——让不兼容的接口握手","类适配器 vs 对象适配器、在 Spring MVC 里找它","2113897628365230081",1,1578,5381,14,"2023-12-11T21:59:29","上一篇《设计模式修炼之路-原型模式》收官了创建型模式：单例、工厂、建造者、原型，四种\"造对象\"的姿势全部到齐。从这篇起进入**结构型模式**——关注的不再是\"怎么造对象\"，而是\"**类和对象之间怎么组装搭配**\"，一共八位选手。开篇登场的，就是生活里最常见的那个小玩意：**适配器模式（Adapter Pattern）**。手里攥着三相插头、墙上却是两孔插座时，你第一个想到的就是它。\n\n## 一、什么是适配器模式？\n\n先看定义：\n\n> 将一个类的接口转换成客户期望的另一个接口，使原本接口不兼容而不能一起工作的类可以一起工作。\n\n翻译成人话：**接口对不上时，谁也不改，中间加一层转换器**。\n\n生活类比不需要想象力：家里墙上插座输出 220V 交流电，手机电池只吃 5V 直流电。你不会去砸墙重新布线（改造供电方），也不会把手机拆开改电路（改造用电方），而是买一个充电头（适配器）夹在中间，一头插墙上、一头接手机，各说各的语言，中间有人翻译。GoF 给它起的别名就很直白——**包装器（Wrapper）**。\n\n适配器模式有三个角色：\n\n- **Target（目标接口）**：客户端想要的接口形态，上例里就是\"输出 5V\"；\n- **Adaptee（被适配者）**：已经存在、但接口对不上的类，上例里就是\"输出 220V\"的插座；\n- **Adapter（适配器）**：中间那层转换器，把 Adaptee 的能力翻译成 Target 的样子。\n\n按实现方式又分三种形态：**类适配器**（靠继承）、**对象适配器**（靠组合）、**接口适配器**（靠空实现兜底）。前两种是主角，第三种是衍生玩法，第四节全部实现。\n\n## 二、适配器模式的优缺点\n\n### 优点\n\n1. **两头都不改**。Target 不动、Adaptee 不动，中间加一层就能协同工作，完美符合开闭原则——对扩展开放，对修改关闭。\n2. **复用存量代码**。老系统的类、第三方的 SDK，包一层就能接入新体系，是\"接盘\"遗留代码改造成本最低的方式。\n3. **对客户端透明**。客户端只认 Target 接口，Adaptee 被彻底藏在适配器背后，换实现客户端毫无感知。\n4. 对象适配器还支持**运行时换被适配者**，构造函数传谁就适配谁。\n\n### 缺点\n\n1. **类适配器受单继承限制**。Java 里 extends 名额只有一个，被 Adaptee 占了就不能再继承别的类；而且类适配器一次只能适配一个特定的 Adaptee。\n2. **对象适配器难以覆写被适配者的行为**。毕竟是组合持有而非继承，想改造 Adaptee 内部逻辑有心无力。\n3. **中间层多了容易套娃**。适配器用上瘾之后，系统里到处是\"包一层再包一层\"，调用链变长，排查问题得多扒好几层皮。\n4. **滥用警告**：如果接口不兼容是自家代码造成的，优先**重构统一接口**，而不是拿适配器当遮羞布——它是给\"改不了、改不起\"的代码准备的急救包，不是日常开发的主力武器。\n\n## 三、适配器模式的应用场景\n\n适配器模式在 JDK 和主流框架里无处不在：\n\n- **JDK 里**：`InputStreamReader` 把 InputStream（字节流）适配成 Reader（字符流）——你写过的 `new BufferedReader(new InputStreamReader(System.in))` 就是两个适配器叠着用；`Arrays.asList()` 把数组适配成 List 视图。\n- **Spring MVC 里**：`HandlerAdapter`——名字直接写在脸上。DispatcherServlet 要面对多种形态的处理器（@Controller 方法、HttpRequestHandler、Servlet……），它不想为每种类型写一坨 if-else，于是给每种处理器配一个适配器，统一调 `adapter.handle(...)`。想支持新处理器类型？加个新 HandlerAdapter 就行，DispatcherServlet 一行不改。\n- **日志门面里**：slf4j 的一堆桥接包（如 log4j-over-slf4j），把老日志框架的 API 调用\"翻译\"到新体系，老代码一行不改。\n- **日常业务里**：对接第三方支付、短信、天气 SDK，对方的参数风格、错误码体系和你内部接口对不上——写个 Adapter 把它包成自家接口的样子，而不是把内部代码改成对方的风格。\n\n**什么时候该用**：想复用一个现成类，但它的接口和你需要的不一致，而且这个类**改不了**（第三方）或**改不起**（老系统在跑、牵一发动全身）。\n\n## 四、适配器模式的实现方式\n\n示例代码在 design-pattern 项目的 adapter 包里：\n\n```\ndesign-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── adapter                      \u002F\u002F 适配器模式\n    ├── Voltage220V.java         \u002F\u002F 被适配者：家用电\n    ├── Voltage5V.java           \u002F\u002F 目标接口：手机要的 5V\n    ├── ClassAdapter.java        \u002F\u002F 类适配器（继承）\n    ├── ObjectAdapter.java       \u002F\u002F 对象适配器（组合）\n    ├── Phone.java               \u002F\u002F 手机：只认 5V\n    ├── AdapterTest.java         \u002F\u002F 测试入口\n    └── interfaceadapter         \u002F\u002F 接口适配器\n        ├── Animal.java          \u002F\u002F 方法太多的接口\n        ├── AnimalAdapter.java   \u002F\u002F 全部空实现的抽象类\n        └── Duck.java            \u002F\u002F 只覆写关心的方法\n```\n\n场景：手机只认 5V，墙上插座输出 220V——别砸墙，也别拆手机，加个充电头。\n\n### 1. 类适配器：继承 + 实现\n\n先看两位\"接口对不上\"的当事人。被适配者，墙上的电：\n\n```java\npublic class Voltage220V {\n\n    public int output220V() {\n        int src = 220;\n        System.out.println(\"被适配者输出电压：\" + src + \"V\");\n        return src;\n    }\n}\n```\n\n目标接口，手机要的电：\n\n```java\npublic interface Voltage5V {\n\n    int output5V();\n}\n```\n\n类适配器的写法：**继承被适配者，同时实现目标接口**，在接口方法里调用继承来的能力做转换：\n\n```java\n\u002F\u002F 类适配器：继承被适配者，实现目标接口\npublic class ClassAdapter extends Voltage220V implements Voltage5V {\n\n    @Override\n    public int output5V() {\n        int srcV = output220V();\n        int dstV = srcV \u002F 44; \u002F\u002F 220V 转 5V，模拟变压器\n        System.out.println(\"类适配器转换后输出电压：\" + dstV + \"V\");\n        return dstV;\n    }\n}\n```\n\n用电方手机，它眼里只有 Voltage5V：\n\n```java\npublic class Phone {\n\n    public void charging(Voltage5V voltage5V) {\n        if (voltage5V.output5V() == 5) {\n            System.out.println(\"电压正常，手机开始充电\");\n        } else {\n            System.out.println(\"电压异常，拒绝充电\");\n        }\n    }\n}\n```\n\n测试代码：\n\n```java\nprivate static void classAdapter() {\n    Phone phone = new Phone();\n    phone.charging(new ClassAdapter());\n}\n```\n\n运行结果：\n\n```\n===== 类适配器 =====\n被适配者输出电压：220V\n类适配器转换后输出电压：5V\n电压正常，手机开始充电\n```\n\n通了。ClassAdapter 既是 220V（继承来的），又是 5V（实现来的），`output5V()` 内部调 `output220V()` 完成降压——手机从头到尾没见过 220V。\n\n但痛点马上浮出来：**Java 单继承**。这个 extends 名额被 Voltage220V 占死后，适配器就不能再继承别的类了；而且继承是\"类级绑定\"，Adaptee 被写死在 extends 后面——哪天要适配美标 110V，只能另写一个适配器类。僵。\n\n### 2. 对象适配器：组合 + 实现\n\n于是教科书和《Effective Java》口径一致：**用组合代替继承**。不再 extends 被适配者，而是把它当字段持有：\n\n```java\n\u002F\u002F 对象适配器：组合被适配者，实现目标接口\npublic class ObjectAdapter implements Voltage5V {\n\n    private final Voltage220V voltage220V;\n\n    public ObjectAdapter(Voltage220V voltage220V) {\n        this.voltage220V = voltage220V;\n    }\n\n    @Override\n    public int output5V() {\n        int srcV = voltage220V.output220V();\n        int dstV = srcV \u002F 44; \u002F\u002F 220V 转 5V，模拟变压器\n        System.out.println(\"对象适配器转换后输出电压：\" + dstV + \"V\");\n        return dstV;\n    }\n}\n```\n\n测试代码：\n\n```java\nprivate static void objectAdapter() {\n    Phone phone = new Phone();\n    phone.charging(new ObjectAdapter(new Voltage220V()));\n}\n```\n\n运行结果：\n\n```\n===== 对象适配器 =====\n被适配者输出电压：220V\n对象适配器转换后输出电压：5V\n电压正常，手机开始充电\n```\n\n效果一模一样，内部结构完全不同。变化全在组合带来的自由度上：\n\n- 适配器不再占用继承名额，该 extends 谁 extends 谁；\n- 被适配者通过构造函数注入，**传 Voltage220V 还是它的任何子类都行**——不再绑定一个类，而是适配\"一族\"对象；\n- 运行时想换被适配者实例，传新的就是；\n- 甚至可以在一个适配器里组合多个被适配者一起干活。\n\n这是\"组合优于继承\"在结构型模式里的第一次大显身手，后面桥接、装饰器、代理还会反复请它出场。另外注意 `Phone.charging(Voltage5V)` 的参数是接口——手机依赖的是抽象而不是具体电源，这正是适配器能成立的根基（第六篇单例时埋的\"依赖倒置\"伏笔，又应验了一次）。\n\n### 3. 接口适配器：空实现兜底\n\n第三种形态换个场景：接口方法太多，实现类用不上几个，却被迫给每个方法写空实现。比如一个 Animal 接口要你实现飞、游、跑、叫四件事：\n\n```java\npublic interface Animal {\n\n    void fly();\n\n    void swim();\n\n    void run();\n\n    void bark();\n}\n```\n\n鸭子不会飞也不会叫（至少我们的鸭子不会），直接 implements 就得陪跑两个空方法。解法是先来一个\"全空实现\"的抽象类：\n\n```java\n\u002F\u002F 接口适配器：抽象类把全部方法给出空实现\npublic abstract class AnimalAdapter implements Animal {\n\n    @Override\n    public void fly() {\n    }\n\n    @Override\n    public void swim() {\n    }\n\n    @Override\n    public void run() {\n    }\n\n    @Override\n    public void bark() {\n    }\n}\n```\n\n鸭子只继承这个抽象类，覆写关心的方法：\n\n```java\n\u002F\u002F 只覆写关心的方法，其余方法不用陪跑\npublic class Duck extends AnimalAdapter {\n\n    @Override\n    public void swim() {\n        System.out.println(\"鸭子在游泳\");\n    }\n\n    @Override\n    public void run() {\n        System.out.println(\"鸭子摇摇摆摆地跑\");\n    }\n}\n```\n\n测试代码：\n\n```java\nprivate static void interfaceAdapter() {\n    Duck duck = new Duck();\n    duck.swim();\n    duck.run();\n}\n```\n\n运行结果：\n\n```\n===== 接口适配器 =====\n鸭子在游泳\n鸭子摇摇摆摆地跑\n```\n\nJDK 里最典型的就是 `WindowAdapter`：MouseListener 接口有 5 个方法，你只想处理点击，剩下四个空着——继承适配器类只覆写一个就行。老 Swing 代码里它和匿名内部类是黄金搭档。\n\n注意它的本质：前两种适配器是\"**把 A 的接口翻译成 B**\"，接口适配器是\"**把一个太重的接口稀释成你要的浓度**\"，严格说只是借了个名分，但面试常放在一起问，值得知道。\n\n## 五、类适配器 vs 对象适配器怎么选\n\n两种形态一字之差（extends 换成了字段），气质却大不相同：\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>一个特定的 Adaptee\u003C\u002Ftd>\u003Ctd>Adaptee 及其所有子类\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>覆写 Adaptee 行为\u003C\u002Ftd>\u003Ctd>方便（直接重写）\u003C\u002Ftd>\u003Ctd>困难\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>运行时换被适配者\u003C\u002Ftd>\u003Ctd>不行\u003C\u002Ftd>\u003Ctd>可以（构造注入）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Java 里的推荐度\u003C\u002Ftd>\u003Ctd>受单继承限制\u003C\u002Ftd>\u003Ctd>**首选**\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n结论一句话：**默认对象适配器**，只有当你确实需要改造（覆写）被适配者的部分行为时，才考虑类适配器。它俩的区别，本质上就是\"继承 vs 组合\"这场百年之争在一个模式上的投影。\n\n## 写在最后\n\n回顾这一篇：适配器模式在 Target 和 Adaptee 都不动的前提下，用中间一层转换器让不兼容的接口协同工作，是把\"改不了、改不起\"的存量代码接入新体系的急救包；类适配器靠继承，受单继承限制；对象适配器靠组合，灵活能换，是 Java 首选；接口适配器用全空实现的抽象类解决\"接口方法太多\"的陪跑之苦；Spring MVC 的 HandlerAdapter 则是它在真实框架里的高光时刻——新增处理器类型不用改 DispatcherServlet。\n\n下一篇继续结构型模式，第二位选手：**桥接模式（Bridge）**。同样是\"中间加一层\"，适配器治的是\"接口对不上\"，桥接治的是\"**两个维度一起膨胀**\"——当形状要乘以颜色、品牌要乘以型号，继承图开始爆炸时，就该请它出场了。","\u003Cp>上一篇《设计模式修炼之路-原型模式》收官了创建型模式：单例、工厂、建造者、原型，四种\"造对象\"的姿势全部到齐。从这篇起进入\u003Cstrong>结构型模式\u003C\u002Fstrong>——关注的不再是\"怎么造对象\"，而是\"\u003Cstrong>类和对象之间怎么组装搭配\u003C\u002Fstrong>\"，一共八位选手。开篇登场的，就是生活里最常见的那个小玩意：\u003Cstrong>适配器模式（Adapter 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>生活类比不需要想象力：家里墙上插座输出 220V 交流电，手机电池只吃 5V 直流电。你不会去砸墙重新布线（改造供电方），也不会把手机拆开改电路（改造用电方），而是买一个充电头（适配器）夹在中间，一头插墙上、一头接手机，各说各的语言，中间有人翻译。GoF 给它起的别名就很直白——\u003Cstrong>包装器（Wrapper）\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>适配器模式有三个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Target（目标接口）\u003C\u002Fstrong>：客户端想要的接口形态，上例里就是\"输出 5V\"；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Adaptee（被适配者）\u003C\u002Fstrong>：已经存在、但接口对不上的类，上例里就是\"输出 220V\"的插座；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Adapter（适配器）\u003C\u002Fstrong>：中间那层转换器，把 Adaptee 的能力翻译成 Target 的样子。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>按实现方式又分三种形态：\u003Cstrong>类适配器\u003C\u002Fstrong>（靠继承）、\u003Cstrong>对象适配器\u003C\u002Fstrong>（靠组合）、\u003Cstrong>接口适配器\u003C\u002Fstrong>（靠空实现兜底）。前两种是主角，第三种是衍生玩法，第四节全部实现。\u003C\u002Fp>\n\u003Ch2>二、适配器模式的优缺点\u003C\u002Fh2>\n\u003Ch3>优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>两头都不改\u003C\u002Fstrong>。Target 不动、Adaptee 不动，中间加一层就能协同工作，完美符合开闭原则——对扩展开放，对修改关闭。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>复用存量代码\u003C\u002Fstrong>。老系统的类、第三方的 SDK，包一层就能接入新体系，是\"接盘\"遗留代码改造成本最低的方式。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对客户端透明\u003C\u002Fstrong>。客户端只认 Target 接口，Adaptee 被彻底藏在适配器背后，换实现客户端毫无感知。\u003C\u002Fli>\n \u003Cli>对象适配器还支持\u003Cstrong>运行时换被适配者\u003C\u002Fstrong>，构造函数传谁就适配谁。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>类适配器受单继承限制\u003C\u002Fstrong>。Java 里 extends 名额只有一个，被 Adaptee 占了就不能再继承别的类；而且类适配器一次只能适配一个特定的 Adaptee。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对象适配器难以覆写被适配者的行为\u003C\u002Fstrong>。毕竟是组合持有而非继承，想改造 Adaptee 内部逻辑有心无力。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>中间层多了容易套娃\u003C\u002Fstrong>。适配器用上瘾之后，系统里到处是\"包一层再包一层\"，调用链变长，排查问题得多扒好几层皮。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>滥用警告\u003C\u002Fstrong>：如果接口不兼容是自家代码造成的，优先\u003Cstrong>重构统一接口\u003C\u002Fstrong>，而不是拿适配器当遮羞布——它是给\"改不了、改不起\"的代码准备的急救包，不是日常开发的主力武器。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、适配器模式的应用场景\u003C\u002Fh2>\n\u003Cp>适配器模式在 JDK 和主流框架里无处不在：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDK 里\u003C\u002Fstrong>：\u003Ccode>InputStreamReader\u003C\u002Fcode> 把 InputStream（字节流）适配成 Reader（字符流）——你写过的 \u003Ccode>new BufferedReader(new InputStreamReader(System.in))\u003C\u002Fcode> 就是两个适配器叠着用；\u003Ccode>Arrays.asList()\u003C\u002Fcode> 把数组适配成 List 视图。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Spring MVC 里\u003C\u002Fstrong>：\u003Ccode>HandlerAdapter\u003C\u002Fcode>——名字直接写在脸上。DispatcherServlet 要面对多种形态的处理器（@Controller 方法、HttpRequestHandler、Servlet……），它不想为每种类型写一坨 if-else，于是给每种处理器配一个适配器，统一调 \u003Ccode>adapter.handle(...)\u003C\u002Fcode>。想支持新处理器类型？加个新 HandlerAdapter 就行，DispatcherServlet 一行不改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>日志门面里\u003C\u002Fstrong>：slf4j 的一堆桥接包（如 log4j-over-slf4j），把老日志框架的 API 调用\"翻译\"到新体系，老代码一行不改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>日常业务里\u003C\u002Fstrong>：对接第三方支付、短信、天气 SDK，对方的参数风格、错误码体系和你内部接口对不上——写个 Adapter 把它包成自家接口的样子，而不是把内部代码改成对方的风格。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：想复用一个现成类，但它的接口和你需要的不一致，而且这个类\u003Cstrong>改不了\u003C\u002Fstrong>（第三方）或\u003Cstrong>改不起\u003C\u002Fstrong>（老系统在跑、牵一发动全身）。\u003C\u002Fp>\n\u003Ch2>四、适配器模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 adapter 包里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>design-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── adapter                      \u002F\u002F 适配器模式\n    ├── Voltage220V.java         \u002F\u002F 被适配者：家用电\n    ├── Voltage5V.java           \u002F\u002F 目标接口：手机要的 5V\n    ├── ClassAdapter.java        \u002F\u002F 类适配器（继承）\n    ├── ObjectAdapter.java       \u002F\u002F 对象适配器（组合）\n    ├── Phone.java               \u002F\u002F 手机：只认 5V\n    ├── AdapterTest.java         \u002F\u002F 测试入口\n    └── interfaceadapter         \u002F\u002F 接口适配器\n        ├── Animal.java          \u002F\u002F 方法太多的接口\n        ├── AnimalAdapter.java   \u002F\u002F 全部空实现的抽象类\n        └── Duck.java            \u002F\u002F 只覆写关心的方法\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：手机只认 5V，墙上插座输出 220V——别砸墙，也别拆手机，加个充电头。\u003C\u002Fp>\n\u003Ch3>1. 类适配器：继承 + 实现\u003C\u002Fh3>\n\u003Cp>先看两位\"接口对不上\"的当事人。被适配者，墙上的电：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class Voltage220V {\n\n    public int output220V() {\n        int src = 220;\n        System.out.println(\"被适配者输出电压：\" + src + \"V\");\n        return src;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>目标接口，手机要的电：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public interface Voltage5V {\n\n    int output5V();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>类适配器的写法：\u003Cstrong>继承被适配者，同时实现目标接口\u003C\u002Fstrong>，在接口方法里调用继承来的能力做转换：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 类适配器：继承被适配者，实现目标接口\npublic class ClassAdapter extends Voltage220V implements Voltage5V {\n\n    @Override\n    public int output5V() {\n        int srcV = output220V();\n        int dstV = srcV \u002F 44; \u002F\u002F 220V 转 5V，模拟变压器\n        System.out.println(\"类适配器转换后输出电压：\" + dstV + \"V\");\n        return dstV;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>用电方手机，它眼里只有 Voltage5V：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class Phone {\n\n    public void charging(Voltage5V voltage5V) {\n        if (voltage5V.output5V() == 5) {\n            System.out.println(\"电压正常，手机开始充电\");\n        } else {\n            System.out.println(\"电压异常，拒绝充电\");\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void classAdapter() {\n    Phone phone = new Phone();\n    phone.charging(new ClassAdapter());\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 类适配器 =====\n被适配者输出电压：220V\n类适配器转换后输出电压：5V\n电压正常，手机开始充电\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>通了。ClassAdapter 既是 220V（继承来的），又是 5V（实现来的），\u003Ccode>output5V()\u003C\u002Fcode> 内部调 \u003Ccode>output220V()\u003C\u002Fcode> 完成降压——手机从头到尾没见过 220V。\u003C\u002Fp>\n\u003Cp>但痛点马上浮出来：\u003Cstrong>Java 单继承\u003C\u002Fstrong>。这个 extends 名额被 Voltage220V 占死后，适配器就不能再继承别的类了；而且继承是\"类级绑定\"，Adaptee 被写死在 extends 后面——哪天要适配美标 110V，只能另写一个适配器类。僵。\u003C\u002Fp>\n\u003Ch3>2. 对象适配器：组合 + 实现\u003C\u002Fh3>\n\u003Cp>于是教科书和《Effective Java》口径一致：\u003Cstrong>用组合代替继承\u003C\u002Fstrong>。不再 extends 被适配者，而是把它当字段持有：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 对象适配器：组合被适配者，实现目标接口\npublic class ObjectAdapter implements Voltage5V {\n\n    private final Voltage220V voltage220V;\n\n    public ObjectAdapter(Voltage220V voltage220V) {\n        this.voltage220V = voltage220V;\n    }\n\n    @Override\n    public int output5V() {\n        int srcV = voltage220V.output220V();\n        int dstV = srcV \u002F 44; \u002F\u002F 220V 转 5V，模拟变压器\n        System.out.println(\"对象适配器转换后输出电压：\" + dstV + \"V\");\n        return dstV;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void objectAdapter() {\n    Phone phone = new Phone();\n    phone.charging(new ObjectAdapter(new Voltage220V()));\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 对象适配器 =====\n被适配者输出电压：220V\n对象适配器转换后输出电压：5V\n电压正常，手机开始充电\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>效果一模一样，内部结构完全不同。变化全在组合带来的自由度上：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>适配器不再占用继承名额，该 extends 谁 extends 谁；\u003C\u002Fli>\n \u003Cli>被适配者通过构造函数注入，\u003Cstrong>传 Voltage220V 还是它的任何子类都行\u003C\u002Fstrong>——不再绑定一个类，而是适配\"一族\"对象；\u003C\u002Fli>\n \u003Cli>运行时想换被适配者实例，传新的就是；\u003C\u002Fli>\n \u003Cli>甚至可以在一个适配器里组合多个被适配者一起干活。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这是\"组合优于继承\"在结构型模式里的第一次大显身手，后面桥接、装饰器、代理还会反复请它出场。另外注意 \u003Ccode>Phone.charging(Voltage5V)\u003C\u002Fcode> 的参数是接口——手机依赖的是抽象而不是具体电源，这正是适配器能成立的根基（第六篇单例时埋的\"依赖倒置\"伏笔，又应验了一次）。\u003C\u002Fp>\n\u003Ch3>3. 接口适配器：空实现兜底\u003C\u002Fh3>\n\u003Cp>第三种形态换个场景：接口方法太多，实现类用不上几个，却被迫给每个方法写空实现。比如一个 Animal 接口要你实现飞、游、跑、叫四件事：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public interface Animal {\n\n    void fly();\n\n    void swim();\n\n    void run();\n\n    void bark();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>鸭子不会飞也不会叫（至少我们的鸭子不会），直接 implements 就得陪跑两个空方法。解法是先来一个\"全空实现\"的抽象类：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 接口适配器：抽象类把全部方法给出空实现\npublic abstract class AnimalAdapter implements Animal {\n\n    @Override\n    public void fly() {\n    }\n\n    @Override\n    public void swim() {\n    }\n\n    @Override\n    public void run() {\n    }\n\n    @Override\n    public void bark() {\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>鸭子只继承这个抽象类，覆写关心的方法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 只覆写关心的方法，其余方法不用陪跑\npublic class Duck extends AnimalAdapter {\n\n    @Override\n    public void swim() {\n        System.out.println(\"鸭子在游泳\");\n    }\n\n    @Override\n    public void run() {\n        System.out.println(\"鸭子摇摇摆摆地跑\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void interfaceAdapter() {\n    Duck duck = new Duck();\n    duck.swim();\n    duck.run();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 接口适配器 =====\n鸭子在游泳\n鸭子摇摇摆摆地跑\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>JDK 里最典型的就是 \u003Ccode>WindowAdapter\u003C\u002Fcode>：MouseListener 接口有 5 个方法，你只想处理点击，剩下四个空着——继承适配器类只覆写一个就行。老 Swing 代码里它和匿名内部类是黄金搭档。\u003C\u002Fp>\n\u003Cp>注意它的本质：前两种适配器是\"\u003Cstrong>把 A 的接口翻译成 B\u003C\u002Fstrong>\"，接口适配器是\"\u003Cstrong>把一个太重的接口稀释成你要的浓度\u003C\u002Fstrong>\"，严格说只是借了个名分，但面试常放在一起问，值得知道。\u003C\u002Fp>\n\u003Ch2>五、类适配器 vs 对象适配器怎么选\u003C\u002Fh2>\n\u003Cp>两种形态一字之差（extends 换成了字段），气质却大不相同：\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>一个特定的 Adaptee\u003C\u002Ftd>\n   \u003Ctd>Adaptee 及其所有子类\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>覆写 Adaptee 行为\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>Java 里的推荐度\u003C\u002Ftd>\n   \u003Ctd>受单继承限制\u003C\u002Ftd>\n   \u003Ctd>**首选**\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>结论一句话：\u003Cstrong>默认对象适配器\u003C\u002Fstrong>，只有当你确实需要改造（覆写）被适配者的部分行为时，才考虑类适配器。它俩的区别，本质上就是\"继承 vs 组合\"这场百年之争在一个模式上的投影。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：适配器模式在 Target 和 Adaptee 都不动的前提下，用中间一层转换器让不兼容的接口协同工作，是把\"改不了、改不起\"的存量代码接入新体系的急救包；类适配器靠继承，受单继承限制；对象适配器靠组合，灵活能换，是 Java 首选；接口适配器用全空实现的抽象类解决\"接口方法太多\"的陪跑之苦；Spring MVC 的 HandlerAdapter 则是它在真实框架里的高光时刻——新增处理器类型不用改 DispatcherServlet。\u003C\u002Fp>\n\u003Cp>下一篇继续结构型模式，第二位选手：\u003Cstrong>桥接模式（Bridge）\u003C\u002Fstrong>。同样是\"中间加一层\"，适配器治的是\"接口对不上\"，桥接治的是\"\u003Cstrong>两个维度一起膨胀\u003C\u002Fstrong>\"——当形状要乘以颜色、品牌要乘以型号，继承图开始爆炸时，就该请它出场了。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,62],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113931625669595138","design-pattern-10","设计模式修炼之路 ⑩ 装饰器模式——不改原类，动态加功能","不改原类动态加功能、\"我是组件+我有组件\"、Java IO 流的套娃设计",1003,7847,20,"2024-02-10T15:14:16",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":52,"publishedAt":61},"2113933638763548673","design-pattern-11","设计模式修炼之路 ⑪ 外观模式——给杂乱子系统一个入口","给杂乱子系统一个统一入口、只编排不干活、SLF4J 名字里的 Facade",3176,7681,"2024-03-20T15:49:38",{"id":63,"slug":64,"title":65,"summary":66,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":67,"likeCount":4,"wordCount":68,"readingMinutes":69,"publishedAt":70},"2113931511261564929","design-pattern-08","设计模式修炼之路 ⑧ 过滤器模式——与或非条件当积木","非 GoF、与或非条件当积木、Predicate 是 JDK 收编版",3801,7442,19,"2024-01-08T20:51:45",{"code":4,"message":5,"data":72},{"prev":73,"next":82},{"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},"2113931247385317377","design-pattern-05","设计模式修炼之路 ⑤ 原型模式——clone() 的浅与深","clone() 与 Cloneable、浅拷贝踩坑、逐字段克隆与序列化两种深拷贝",2661,5758,15,"2023-11-17T17:12:14",{"id":83,"slug":84,"title":85,"summary":86,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":87,"likeCount":4,"wordCount":88,"readingMinutes":36,"publishedAt":89},"2113931455091445761","design-pattern-07","设计模式修炼之路 ⑦ 桥接模式——m×n 降为 m+n","继承爆炸的解药、m×n 降为 m+n、在 JDBC 里找它",1907,5226,"2023-12-15T15:12:08",{"code":4,"message":5,"data":91},[92,98,103,109],{"id":93,"name":94,"slug":95,"description":96,"sortOrder":4,"articleCount":97},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":99,"slug":100,"description":101,"sortOrder":4,"articleCount":102},"设计模式","design-pattern","设计模式系列","34",{"id":104,"name":105,"slug":106,"description":107,"sortOrder":33,"articleCount":108},"1946000000000000002","随笔","essay","生活与思考","0",{"id":110,"name":111,"slug":112,"description":113,"sortOrder":114,"articleCount":115},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":117,"toc":118},"\u003Cp>上一篇《设计模式修炼之路-原型模式》收官了创建型模式：单例、工厂、建造者、原型，四种\"造对象\"的姿势全部到齐。从这篇起进入\u003Cstrong>结构型模式\u003C\u002Fstrong>——关注的不再是\"怎么造对象\"，而是\"\u003Cstrong>类和对象之间怎么组装搭配\u003C\u002Fstrong>\"，一共八位选手。开篇登场的，就是生活里最常见的那个小玩意：\u003Cstrong>适配器模式（Adapter 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>生活类比不需要想象力：家里墙上插座输出 220V 交流电，手机电池只吃 5V 直流电。你不会去砸墙重新布线（改造供电方），也不会把手机拆开改电路（改造用电方），而是买一个充电头（适配器）夹在中间，一头插墙上、一头接手机，各说各的语言，中间有人翻译。GoF 给它起的别名就很直白——\u003Cstrong>包装器（Wrapper）\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>适配器模式有三个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Target（目标接口）\u003C\u002Fstrong>：客户端想要的接口形态，上例里就是\"输出 5V\"；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Adaptee（被适配者）\u003C\u002Fstrong>：已经存在、但接口对不上的类，上例里就是\"输出 220V\"的插座；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Adapter（适配器）\u003C\u002Fstrong>：中间那层转换器，把 Adaptee 的能力翻译成 Target 的样子。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>按实现方式又分三种形态：\u003Cstrong>类适配器\u003C\u002Fstrong>（靠继承）、\u003Cstrong>对象适配器\u003C\u002Fstrong>（靠组合）、\u003Cstrong>接口适配器\u003C\u002Fstrong>（靠空实现兜底）。前两种是主角，第三种是衍生玩法，第四节全部实现。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、适配器模式的优缺点\u003C\u002Fh2>\n\u003Ch3 id=\"toc-3\">优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>两头都不改\u003C\u002Fstrong>。Target 不动、Adaptee 不动，中间加一层就能协同工作，完美符合开闭原则——对扩展开放，对修改关闭。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>复用存量代码\u003C\u002Fstrong>。老系统的类、第三方的 SDK，包一层就能接入新体系，是\"接盘\"遗留代码改造成本最低的方式。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对客户端透明\u003C\u002Fstrong>。客户端只认 Target 接口，Adaptee 被彻底藏在适配器背后，换实现客户端毫无感知。\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>。Java 里 extends 名额只有一个，被 Adaptee 占了就不能再继承别的类；而且类适配器一次只能适配一个特定的 Adaptee。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对象适配器难以覆写被适配者的行为\u003C\u002Fstrong>。毕竟是组合持有而非继承，想改造 Adaptee 内部逻辑有心无力。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>中间层多了容易套娃\u003C\u002Fstrong>。适配器用上瘾之后，系统里到处是\"包一层再包一层\"，调用链变长，排查问题得多扒好几层皮。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>滥用警告\u003C\u002Fstrong>：如果接口不兼容是自家代码造成的，优先\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>JDK 里\u003C\u002Fstrong>：\u003Ccode>InputStreamReader\u003C\u002Fcode> 把 InputStream（字节流）适配成 Reader（字符流）——你写过的 \u003Ccode>new BufferedReader(new InputStreamReader(System.in))\u003C\u002Fcode> 就是两个适配器叠着用；\u003Ccode>Arrays.asList()\u003C\u002Fcode> 把数组适配成 List 视图。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Spring MVC 里\u003C\u002Fstrong>：\u003Ccode>HandlerAdapter\u003C\u002Fcode>——名字直接写在脸上。DispatcherServlet 要面对多种形态的处理器（@Controller 方法、HttpRequestHandler、Servlet……），它不想为每种类型写一坨 if-else，于是给每种处理器配一个适配器，统一调 \u003Ccode>adapter.handle(...)\u003C\u002Fcode>。想支持新处理器类型？加个新 HandlerAdapter 就行，DispatcherServlet 一行不改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>日志门面里\u003C\u002Fstrong>：slf4j 的一堆桥接包（如 log4j-over-slf4j），把老日志框架的 API 调用\"翻译\"到新体系，老代码一行不改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>日常业务里\u003C\u002Fstrong>：对接第三方支付、短信、天气 SDK，对方的参数风格、错误码体系和你内部接口对不上——写个 Adapter 把它包成自家接口的样子，而不是把内部代码改成对方的风格。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：想复用一个现成类，但它的接口和你需要的不一致，而且这个类\u003Cstrong>改不了\u003C\u002Fstrong>（第三方）或\u003Cstrong>改不起\u003C\u002Fstrong>（老系统在跑、牵一发动全身）。\u003C\u002Fp>\n\u003Ch2 id=\"toc-6\">四、适配器模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 adapter 包里：\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>└── adapter                      \u002F\u002F 适配器模式\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Voltage220V.java         \u002F\u002F 被适配者：家用电\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Voltage5V.java           \u002F\u002F 目标接口：手机要的 5V\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── ClassAdapter.java        \u002F\u002F 类适配器（继承）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── ObjectAdapter.java       \u002F\u002F 对象适配器（组合）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Phone.java               \u002F\u002F 手机：只认 5V\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── AdapterTest.java         \u002F\u002F 测试入口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    └── interfaceadapter         \u002F\u002F 接口适配器\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        ├── Animal.java          \u002F\u002F 方法太多的接口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        ├── AnimalAdapter.java   \u002F\u002F 全部空实现的抽象类\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        └── Duck.java            \u002F\u002F 只覆写关心的方法\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：手机只认 5V，墙上插座输出 220V——别砸墙，也别拆手机，加个充电头。\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\"> Voltage220V\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\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> output220V\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> src \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 220\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\"> src \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"V\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> src;\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>目标接口，手机要的电：\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\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Voltage5V\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\">    int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> output5V\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\"> ClassAdapter\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Voltage220V\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Voltage5V\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\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> output5V\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> srcV \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> output220V\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> dstV \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> srcV \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 44\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 220V 转 5V，模拟变压器\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\"> dstV \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"V\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> dstV;\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>用电方手机，它眼里只有 Voltage5V：\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\"> Phone\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\"> charging\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Voltage5V \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">voltage5V\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (voltage5V.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">output5V\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 5\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:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\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:#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\">\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 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\"> classAdapter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Phone phone \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\"> Phone\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    phone.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">charging\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ClassAdapter\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>被适配者输出电压：220V\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>类适配器转换后输出电压：5V\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>电压正常，手机开始充电\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>通了。ClassAdapter 既是 220V（继承来的），又是 5V（实现来的），\u003Ccode>output5V()\u003C\u002Fcode> 内部调 \u003Ccode>output220V()\u003C\u002Fcode> 完成降压——手机从头到尾没见过 220V。\u003C\u002Fp>\n\u003Cp>但痛点马上浮出来：\u003Cstrong>Java 单继承\u003C\u002Fstrong>。这个 extends 名额被 Voltage220V 占死后，适配器就不能再继承别的类了；而且继承是\"类级绑定\"，Adaptee 被写死在 extends 后面——哪天要适配美标 110V，只能另写一个适配器类。僵。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">2. 对象适配器：组合 + 实现\u003C\u002Fh3>\n\u003Cp>于是教科书和《Effective Java》口径一致：\u003Cstrong>用组合代替继承\u003C\u002Fstrong>。不再 extends 被适配者，而是把它当字段持有：\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\"> ObjectAdapter\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Voltage5V\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:#24292E;--shiki-dark:#E1E4E8\"> Voltage220V voltage220V;\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\"> ObjectAdapter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Voltage220V \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">voltage220V\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\">.voltage220V \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> voltage220V;\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\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> output5V\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> srcV \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> voltage220V.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">output220V\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> dstV \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> srcV \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 44\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 220V 转 5V，模拟变压器\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\"> dstV \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"V\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> dstV;\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>测试代码：\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\"> objectAdapter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Phone phone \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\"> Phone\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    phone.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">charging\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ObjectAdapter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Voltage220V\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>被适配者输出电压：220V\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>对象适配器转换后输出电压：5V\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>电压正常，手机开始充电\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>适配器不再占用继承名额，该 extends 谁 extends 谁；\u003C\u002Fli>\n \u003Cli>被适配者通过构造函数注入，\u003Cstrong>传 Voltage220V 还是它的任何子类都行\u003C\u002Fstrong>——不再绑定一个类，而是适配\"一族\"对象；\u003C\u002Fli>\n \u003Cli>运行时想换被适配者实例，传新的就是；\u003C\u002Fli>\n \u003Cli>甚至可以在一个适配器里组合多个被适配者一起干活。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这是\"组合优于继承\"在结构型模式里的第一次大显身手，后面桥接、装饰器、代理还会反复请它出场。另外注意 \u003Ccode>Phone.charging(Voltage5V)\u003C\u002Fcode> 的参数是接口——手机依赖的是抽象而不是具体电源，这正是适配器能成立的根基（第六篇单例时埋的\"依赖倒置\"伏笔，又应验了一次）。\u003C\u002Fp>\n\u003Ch3 id=\"toc-9\">3. 接口适配器：空实现兜底\u003C\u002Fh3>\n\u003Cp>第三种形态换个场景：接口方法太多，实现类用不上几个，却被迫给每个方法写空实现。比如一个 Animal 接口要你实现飞、游、跑、叫四件事：\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\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Animal\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\"> fly\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\"> swim\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\"> run\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\"> bark\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>鸭子不会飞也不会叫（至少我们的鸭子不会），直接 implements 就得陪跑两个空方法。解法是先来一个\"全空实现\"的抽象类：\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\"> AnimalAdapter\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Animal\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\"> fly\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>\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\"> swim\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>\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\"> run\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>\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\"> bark\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>鸭子只继承这个抽象类，覆写关心的方法：\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\"> Duck\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> AnimalAdapter\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\"> swim\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:#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>\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\"> run\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:#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>测试代码：\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\"> interfaceAdapter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Duck duck \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\"> Duck\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    duck.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">swim\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    duck.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">run\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>鸭子在游泳\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>鸭子摇摇摆摆地跑\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>JDK 里最典型的就是 \u003Ccode>WindowAdapter\u003C\u002Fcode>：MouseListener 接口有 5 个方法，你只想处理点击，剩下四个空着——继承适配器类只覆写一个就行。老 Swing 代码里它和匿名内部类是黄金搭档。\u003C\u002Fp>\n\u003Cp>注意它的本质：前两种适配器是\"\u003Cstrong>把 A 的接口翻译成 B\u003C\u002Fstrong>\"，接口适配器是\"\u003Cstrong>把一个太重的接口稀释成你要的浓度\u003C\u002Fstrong>\"，严格说只是借了个名分，但面试常放在一起问，值得知道。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">五、类适配器 vs 对象适配器怎么选\u003C\u002Fh2>\n\u003Cp>两种形态一字之差（extends 换成了字段），气质却大不相同：\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>一个特定的 Adaptee\u003C\u002Ftd>\n   \u003Ctd>Adaptee 及其所有子类\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>覆写 Adaptee 行为\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>Java 里的推荐度\u003C\u002Ftd>\n   \u003Ctd>受单继承限制\u003C\u002Ftd>\n   \u003Ctd>**首选**\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>结论一句话：\u003Cstrong>默认对象适配器\u003C\u002Fstrong>，只有当你确实需要改造（覆写）被适配者的部分行为时，才考虑类适配器。它俩的区别，本质上就是\"继承 vs 组合\"这场百年之争在一个模式上的投影。\u003C\u002Fp>\n\u003Ch2 id=\"toc-11\">写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：适配器模式在 Target 和 Adaptee 都不动的前提下，用中间一层转换器让不兼容的接口协同工作，是把\"改不了、改不起\"的存量代码接入新体系的急救包；类适配器靠继承，受单继承限制；对象适配器靠组合，灵活能换，是 Java 首选；接口适配器用全空实现的抽象类解决\"接口方法太多\"的陪跑之苦；Spring MVC 的 HandlerAdapter 则是它在真实框架里的高光时刻——新增处理器类型不用改 DispatcherServlet。\u003C\u002Fp>\n\u003Cp>下一篇继续结构型模式，第二位选手：\u003Cstrong>桥接模式（Bridge）\u003C\u002Fstrong>。同样是\"中间加一层\"，适配器治的是\"接口对不上\"，桥接治的是\"\u003Cstrong>两个维度一起膨胀\u003C\u002Fstrong>\"——当形状要乘以颜色、品牌要乘以型号，继承图开始爆炸时，就该请它出场了。\u003C\u002Fp>",[119,122,125,129,132,135,138,141,144,147,150],{"id":120,"text":121,"level":114},"toc-1","一、什么是适配器模式？",{"id":123,"text":124,"level":114},"toc-2","二、适配器模式的优缺点",{"id":126,"text":127,"level":128},"toc-3","优点",3,{"id":130,"text":131,"level":128},"toc-4","缺点",{"id":133,"text":134,"level":114},"toc-5","三、适配器模式的应用场景",{"id":136,"text":137,"level":114},"toc-6","四、适配器模式的实现方式",{"id":139,"text":140,"level":128},"toc-7","1. 类适配器：继承 + 实现",{"id":142,"text":143,"level":128},"toc-8","2. 对象适配器：组合 + 实现",{"id":145,"text":146,"level":128},"toc-9","3. 接口适配器：空实现兜底",{"id":148,"text":149,"level":114},"toc-10","五、类适配器 vs 对象适配器怎么选",{"id":151,"text":152,"level":114},"toc-11","写在最后"]