1. 项目概述:当女儿国遇上适配器模式
在《Java设计模式西游篇》这个系列中,作者用西游记的故事来讲解设计模式,既生动又有趣。第八回讲的是适配器模式,用"女儿国语言无障碍"这个场景来比喻适配器模式的作用,非常形象。
适配器模式就像是一个翻译官,让讲不同语言的人能够互相交流。在女儿国的故事里,唐僧师徒和女儿国的居民语言不通,这时候就需要一个适配器(翻译)来沟通。同样在编程中,当两个接口不兼容时,适配器模式就能派上用场。
2. 适配器模式的核心原理
2.1 什么是适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许接口不兼容的类能够一起工作。就像电源适配器能让不同标准的插头正常工作一样,在代码中,适配器模式充当两个不兼容接口之间的桥梁。
适配器模式主要解决"接口不匹配"的问题。在Java中,我们经常会遇到这样的情况:想使用一个类,但它提供的接口与我们需要的接口不一致,这时候就可以考虑使用适配器模式。
2.2 适配器模式的三种形式
适配器模式有三种实现方式:
- 类适配器:通过继承来实现适配
- 对象适配器:通过组合来实现适配
- 接口适配器:通过抽象类来实现适配
在Java中,对象适配器使用得最多,因为它更灵活,符合组合优于继承的原则。
2.3 适配器模式的UML结构
适配器模式主要包含三个角色:
- Target(目标接口):客户端期望的接口
- Adaptee(被适配者):需要被适配的现有接口
- Adapter(适配器):将Adaptee适配成Target
3. 女儿国场景的代码实现
3.1 场景设定
让我们用代码来还原西游记中的场景:
- 唐僧师徒说的"普通话"是我们的目标接口(Target)
- 女儿国居民说的"女儿语"是被适配的接口(Adaptee)
- 我们需要一个翻译(Adapter)来让双方沟通
3.2 代码实现
首先定义目标接口:
// 目标接口:普通话 public interface Mandarin { String speak(); }然后是被适配的类:
// 被适配者:女儿语 public class DaughterLanguage { public String sayInDaughterLanguage() { return "女儿国欢迎你"; // 实际女儿国语言可能是其他形式 } }接着实现适配器:
// 适配器:翻译官 public class LanguageAdapter implements Mandarin { private DaughterLanguage daughterLanguage; public LanguageAdapter(DaughterLanguage daughterLanguage) { this.daughterLanguage = daughterLanguage; } @Override public String speak() { String daughterWords = daughterLanguage.sayInDaughterLanguage(); // 这里进行语言转换逻辑 return translate(daughterWords); } private String translate(String daughterWords) { // 简化的翻译逻辑 if("女儿国欢迎你".equals(daughterWords)) { return "欢迎来到女儿国"; } return "翻译结果"; } }最后是客户端使用:
public class TangMonk { public static void main(String[] args) { DaughterLanguage daughterLanguage = new DaughterLanguage(); Mandarin translator = new LanguageAdapter(daughterLanguage); System.out.println("女儿国居民说: " + translator.speak()); } }4. 适配器模式的实际应用场景
4.1 Java I/O中的适配器
Java I/O库大量使用了适配器模式。比如:
InputStreamReader reader = new InputStreamReader(inputStream);这里InputStreamReader就是一个适配器,它将字节流InputStream适配成字符流Reader。
4.2 Spring中的适配器
Spring MVC中的HandlerAdapter就是典型的适配器模式应用。不同的Controller有不同的处理方式,HandlerAdapter将它们统一适配成DispatcherServlet可以调用的形式。
4.3 旧系统改造
在系统升级改造时,经常需要让新代码和旧代码协同工作。适配器模式可以很好地解决这个问题,避免大规模重写旧代码。
5. 适配器模式的优缺点
5.1 优点
- 可以让任何两个没有关联的类一起运行
- 提高了类的复用性
- 增加了类的透明度
- 灵活性好
5.2 缺点
- 过多使用适配器会让系统变得凌乱
- 由于Java不支持多重继承,所以类适配器有一定的局限性
6. 适配器模式的最佳实践
6.1 何时使用适配器模式
以下情况考虑使用适配器模式:
- 想使用一个已存在的类,但其接口不符合需求
- 想创建一个可复用的类,该类可以与其他不相关的类协同工作
- 想使用一些已经存在的子类,但不可能对每个子类都进行适配
6.2 实现注意事项
- 对象适配器和类适配器有不同的权衡
- 适配器模式不是为了解决还处于开发阶段的问题,而是解决已有问题
- 可以在适配器中增加一些额外的功能,使适配器功能更强大
7. 适配器模式与其他模式的关系
7.1 适配器模式 vs 装饰器模式
适配器模式改变对象的接口,而装饰器模式增强对象的功能。适配器模式是为了让两个接口协同工作,装饰器模式是为了给对象增加新功能。
7.2 适配器模式 vs 代理模式
适配器模式关注接口转换,代理模式关注控制访问。代理模式通常代表一个单一对象,适配器模式通常代表多个后端对象。
7.3 适配器模式 vs 外观模式
外观模式定义了一个新的接口,而适配器模式复用一个已有的接口。适配器使两个已有接口协同工作,而不是定义一个全新的接口。
8. 常见面试问题
8.1 适配器模式的典型应用场景
面试中常被问到适配器模式的使用场景。可以回答:
- 系统需要使用现有的类,但这些类的接口不符合系统的需要
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的类一起工作
- 需要一个统一的输出接口,但是输入类型不可预知
8.2 适配器模式的三种实现方式区别
类适配器:
- 使用继承实现
- 只能适配一个类
- 不需要重新实现整个被适配者
对象适配器:
- 使用组合实现
- 可以适配多个被适配者
- 更灵活,推荐使用
接口适配器:
- 使用抽象类实现
- 可以为接口提供默认实现
- 适用于不想实现所有方法的情况
8.3 适配器模式在JDK中的应用举例
- java.util.Arrays#asList()
- java.io.InputStreamReader(InputStream)
- java.io.OutputStreamWriter(OutputStream)
- javax.xml.bind.annotation.adapters.XmlAdapter
9. 实际项目中的经验分享
9.1 性能考虑
适配器模式会引入额外的间接层,可能对性能有轻微影响。在性能敏感的场景中,需要权衡是否使用适配器模式。
9.2 过度使用的问题
适配器模式不应该被滥用。如果接口不匹配的问题很多,可能需要重新设计系统架构,而不是到处使用适配器。
9.3 测试注意事项
测试适配器时,需要同时测试:
- 适配器是否正确实现了目标接口
- 适配器是否正确调用了被适配者的方法
- 适配器是否正确处理了各种边界情况
10. 扩展思考
10.1 双向适配器
有时候我们需要双向适配,即A可以适配B,B也可以适配A。这种情况下可以实现双向适配器,同时实现两个接口。
10.2 适配器模式与依赖倒置原则
适配器模式很好地体现了依赖倒置原则(DIP),因为它让高层模块不直接依赖低层模块,而是依赖于抽象。
10.3 现代Java中的适配器
随着Java的发展,现在有些场景可以用函数式接口或方法引用来简化适配器的实现。例如:
Function<String, Integer> adapter = Integer::parseInt;这种写法比传统的适配器类更简洁,但只适用于简单场景。