什么是适配器模式(Adapter Pattern)

适配器模式允许将一个类的接口转换成客户端期望的另一个接口。它使得原本由于接口不兼容而不能一起工作的类可以协同工作。
最经典的例子就是电源适配器:你的笔记本电脑(客户端 Client)需要一个特定电压和插头形状的电源(目标接口 Target)。但是墙上的插座(被适配者 Adaptee)提供的电压和插头形状可能不同。电源适配器(适配器 Adapter)就在中间,它接收墙上插座的输出,将其转换为笔记本电脑可以接受的输入。
适配器模式的核心在于创建一个中间层(适配器),弥合现有接口与所需接口之间的差异。

核心思想

适配器模式主要有两种实现方式:
  1. 对象适配器 (Object Adapter): 这是更常用、更灵活的方式,基于组合 (Composition)。
      • Target: 定义客户端使用的目标接口。
      • Adaptee: 定义已经存在、但接口与 Target 不兼容的类。
      • Adapter: 实现 Target 接口。它内部持有一个 Adaptee 对象的实例。当客户端调用 Adapter 的方法时,Adapter 会将这些调用转换并委托给内部持有的 Adaptee 实例的相应方法。
      • Client: 通过 Target 接口与 Adapter 对象交互,完全不知道 Adaptee 的存在。
  1. 类适配器 (Class Adapter): 基于继承 (Inheritance)。
      • Target: 同上。
      • Adaptee: 同上。
      • Adapter: 同时继承 Adaptee 类并实现 Target 接口。它覆盖 Target 接口的方法,并在实现中调用继承自 Adaptee 的方法。
      • Client: 同上。
      • 注意: 类适配器在像 Java/Kotlin 这样不支持多重类继承的语言中受限(但可以继承一个类并实现多个接口)。它也把 Adapter 和 Adaptee 紧密耦合在了一起。因此,对象适配器通常是更推荐的方式。
主要关注对象适配器。

优点

  1. 提高类的复用性 (Reusability): 可以复用现有的 Adaptee 类,而无需修改其源代码。
  1. 解耦 (Decoupling): 将客户端与具体的 Adaptee 实现解耦。客户端只需要依赖 Target 接口。
  1. 更好的单一职责 (Single Responsibility): 适配逻辑被封装在 Adapter 类中,使得 Adaptee 类和客户端代码都更纯粹。
  1. 透明性: 客户端可以像使用其他实现了 Target 接口的类一样使用 Adapter,对客户端来说是透明的。

可能的缺点

  1. 增加复杂性: 引入了额外的 Adapter 类,增加了系统的类的数量和结构复杂度。
  1. 额外的间接层: 调用需要通过 Adapter 进行转发,可能会带来非常微小的性能开销(但在绝大多数情况下可以忽略不计)。
  1. 可能需要适配 Adaptee 的整个接口: 有时为了适配一个方法,可能需要为 Adaptee 的多个相关方法都提供适配逻辑。

使用场景

  1. 当你希望使用一个已经存在的类,但它的接口不符合你的需求时。
  1. 当你希望创建可复用的类,该类可以与一些不相关的或者尚未预见的类(接口不兼容的类)协同工作时。
  1. 当你需要适配不同数据格式时,例如将 XML 数据适配成 JSON 数据,或者将后端 API 返回的数据模型适配成本地应用所需的领域模型。
  1. 集成第三方库或遗留系统: 这是非常常见的场景,第三方库的接口往往与你系统内部的接口不一致。
  1. Android 中的场景:
      • RecyclerView.Adapter: 这是 Android 中最广为人知的适配器模式应用。它扮演了 Adapter 的角色,将你的数据集合(List<YourData>,可以看作 Adaptee 的数据源)适配成 RecyclerView(Client)能够理解和显示的 ViewHolder 对象(Target 的表现形式)。RecyclerView.Adapter 实现了 RecyclerView 需要的接口(如 onCreateViewHolder, onBindViewHolder, getItemCount),并在内部处理你的数据列表。
      • ArrayAdapter / SimpleAdapter / CursorAdapter (用于 ListView/Spinner 等): 这些是早期 Android UI 组件使用的适配器,同样是将数据源(数组、列表、数据库 Cursor)适配成 View 项供列表组件显示。
      • 封装旧 API 或第三方 SDK: 如果你使用了某个 Java 库或者一个旧的 Android API,它的接口使用起来不方便或者与你项目中的设计风格(如回调方式、方法签名)不符,你可以编写一个 Adapter 类来封装它,提供一个更现代、更符合项目规范的接口。
      • 数据层适配: 从网络或数据库获取的数据 DTO (Data Transfer Object) 可能与你的领域模型 (Domain Model) 或 UI 需要的视图模型 (ViewModel) 结构不同。你可以创建一个 Adapter(或 Mapper)将 DTO 转换成所需的模型对象。

UML 图

下面是基于一个将“旧版矩形接口”适配到“新版形状接口”的对象适配器示例 UML 图:
  • ModernShape (Target): 客户端期望使用的接口,包含 render() 和 getSurfaceArea()。
  • LegacyRectangle (Adaptee): 已有的类,接口不兼容,包含 drawShape() 和 calculateArea()。
  • ShapeAdapter (Adapter): 实现了 ModernShape 接口。它内部持有一个 LegacyRectangle 的实例 (legacyRectangle)。它的 render() 方法会调用 legacyRectangle.drawShape(),getSurfaceArea() 方法会调用 legacyRectangle.calculateArea()。
  • Client: 只依赖 ModernShape 接口。它可以接收一个 ShapeAdapter 实例(因为 ShapeAdapter 实现了 ModernShape),并像使用其他 ModernShape 对象一样使用它。

代码示例

在这个例子中:
  • 客户端代码 (main 和 callClientCode) 只想使用 ModernShape 接口。
  • LegacyRectangle 类功能是我们需要的,但接口 (drawShape, calculateArea) 不匹配。
  • ShapeAdapter 类实现了 ModernShape 接口,并在内部持有一个 LegacyRectangle 实例。它将 render 调用转换为 drawShape 调用,将 getSurfaceArea 调用转换为 calculateArea 调用。
  • 最终,客户端可以通过 ModernShape 接口无缝地使用 ShapeAdapter 包装过的 LegacyRectangle,达到了接口适配的目的。

总结

  • 适配器模式是一种非常实用的结构型模式,它通过引入一个适配器层来解决接口不兼容的问题。
  • 它允许我们复用现有的类,同时让客户端代码保持对目标接口的依赖,提高了系统的灵活性和可维护性。
  • 在 Android 开发中,RecyclerView.Adapter 是其最典型的应用,但它在集成旧代码、第三方库或处理不同数据模型转换时也同样有用。
💡
记住它的核心:接口不匹配?加个转换插头(适配器)!
 
设计模式——行为型-责任链模式设计模式——结构型-代理模式