Adapter — 是什么,面向移动开发的接口转换模式

作者: IT Sectr 发布日期: 2026-02-18 阅读时间: 10 分钟

Adapter — 是一种结构型设计模式,它将一个类的接口转换成客户端期望的另一个接口。在移动开发中,Adapter 最常用于 Android 的 RecyclerView.Adapter 和 iOS 的 UITableViewDataSource。根据 Google I/O(2024)的数据,95% 的 Android 应用使用 RecyclerView 来显示列表,每个应用都需要自己的 Adapter 实现。

要点

  • Adapter — 结构型模式,允许具有不兼容接口的对象协同工作
  • RecyclerView.Adapter — 该模式在 Android 中的实现,将数据转换为列表的 ViewHolder
  • UITableViewDataSource — iOS 中的实现,结合了 Adapter 和 DataSource 模式
  • Object Adapter 使用组合:适配器包含对被适配对象的引用
  • Class Adapter 使用多重继承——仅在 C++ 中可行,Java/Swift/Kotlin 中不可用

什么是 Adapter 模式?

Adapter — 结构型模式,解决接口不兼容的问题。它将一个对象(Adaptee)包装到具有客户端期望接口(Target)的类(Adapter)中。客户端与 Target 一起工作,不知道 Adaptee 的存在。这是 Wrapper 模式的一个变体。

Kotlin
// 具有不兼容接口的现有类
class LegacyAuthApi {
    fun loginWithToken(token: String): Map {
        return mapOf("status" to "ok", "user_id" to 42)
    }
}

// 目标接口(客户端期望的)
interface AuthService {
    suspend fun login(credentials: Credentials): Result
}

// Adapter — 将 LegacyAuthApi 转换为 AuthService
class LegacyAuthAdapter(
    private val legacyApi: LegacyAuthApi
) : AuthService {
    override suspend fun login(credentials: Credentials): Result {
        val token = "${credentials.login}:${credentials.password}"
            .encodeToByteArray().let { Base64.encodeToString(it) }
        val response = legacyApi.loginWithToken(token)
        return if (response["status"] == "ok") {
            Result.success(User(response["user_id"] as Int))
        } else {
            Result.failure(AuthException("Login failed"))
        }
    }
}

LegacyAuthAdapter 将 login(credentials) 调用转换为 loginWithToken(token) 以用于旧 API。客户端(ViewModel)通过 AuthService 接口工作,不知道底层是最新的 Firebase Auth 还是十年前的旧 API。这允许在不更改客户端代码的情况下替换实现。

Android 中的 Adapter:RecyclerView 和 ListAdapter

RecyclerView.Adapter — 是 Android 中最常见的 Adapter 模式实现。它将数据(对象列表)转换为 RecyclerView 在屏幕上显示的 ViewHolder。随着 ListAdapter(Android Architecture Components)的出现,该模式获得了内置的差异计算支持,用于动画展示变化。

Kotlin
class UserAdapter(
    private val onItemClick: (User) -> Unit
) : ListAdapter(DiffCallback()) {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    override fun onBindViewHolder(
        holder: UserViewHolder,
        position: Int
    ) {
        holder.bind(getItem(position), onItemClick)
    }

    class UserViewHolder(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
        
        private val tvName = itemView.findViewById(R.id.tvName)
        
        fun bind(user: User, click: (User) -> Unit) {
            tvName.text = user.name
            itemView.setOnClickListener { click(user) }
        }
    }

    private class DiffCallback : DiffUtil.ItemCallback() {
        override fun areItemsTheSame(old: User, new: User) = old.id == new.id
        override fun areContentsTheSame(old: User, new: User) = old == new
    }
}

UserAdapter 使用带有 DiffUtil 的 ListAdapter 来高效地重绘列表。当数据更新时,DiffCallback 计算旧列表和新列表之间的差异,RecyclerView 仅动画显示变化的元素——而无需重绘整个列表。这是相对于需要手动 notifyDataSetChanged() 的旧版 ListView 的关键优势。

iOS 中的 Adapter:UITableView 和 DiffableDataSource

UITableViewDataSource — 既是 Adapter 模式,也是 DataSource 模式。它将模型数据转换为表格单元格。从 iOS 13 开始,Apple 引入了 DiffableDataSource——一个现代替代品,类似于 Android 中的 ListAdapter,自动计算变化并动画展示更新。

Swift
class UserTableViewAdapter: NSObject, UITableViewDataSource {
    var items: [User] = []
    
    func tableView(_ tableView: UITableView,
                    numberOfRowsInSection section: Int) -> Int {
        items.count
    }
    
    func tableView(_ tableView: UITableView,
                    cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(
            withIdentifier: "UserCell", for: indexPath)
        let user = items[indexPath.row]
        cell.textLabel?.text = user.name
        return cell
    }
}

// 现代版本:DiffableDataSource
enum Section { case main }

class ModernUserAdapter {
    var dataSource: UITableViewDiffableDataSource<Section, User>!
    
    func configure(for tableView: UITableView) {
        dataSource = UITableViewDiffableDataSource(
            tableView: tableView
        ) { tableView, indexPath, user in
            let cell = tableView.dequeueReusableCell(
                withIdentifier: "UserCell", for: indexPath)
            cell.textLabel?.text = user.name
            return cell
        }
    }
    
    func update(with users: [User]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, User>()
        snapshot.appendSections([.main])
        snapshot.appendItems(users)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

ModernUserAdapter 使用 DiffableDataSource 在数据更新时自动动画展示变化:元素通过平滑动画出现、消失或移动。iOS 的 DiffableDataSource 和 Android 的 ListAdapter 使用不同的 API 解决相同的任务,但逻辑相同——差异计算 + 自动动画。

Object Adapter vs Class Adapter

Object Adapter 使用组合:适配器包含对 Adaptee 对象的引用并委托调用给它。Class Adapter 使用继承:适配器继承 Adaptee 并同时实现目标接口。在 Java、Swift 和 Kotlin 中,由于缺少多重继承,Class Adapter 不可行——只剩下 Object Adapter。

特性Object AdapterClass Adapter
机制组合(包含 Adaptee)继承(extends Adaptee)
灵活性更高——Adaptee 可在运行时更改更低——Adaptee 在编译时固定
耦合度弱(通过接口)强(通过继承)
子类适配是——Adaptee 可以是任何子类否——仅具体超类
可用性Java, Kotlin, Swift, C++, C#仅支持多重继承的语言(C++)

在移动开发中,仅使用 Object Adapter。RecyclerView.Adapter 包含对数据和 LayoutInflater 的引用,UITableViewDataSource 包含元素数组。与继承相比,组合使代码更灵活、更可测试。

实际项目中的 Adapter 示例

Adapter 不仅用于列表。让我们看看移动开发实践中的三个场景,其中该模式解决了不兼容组件集成的问题。

场景AdapteeTargetAdapter
集成旧的身份验证库LegacyAuthLib(基于回调)AuthService(suspend)LegacyAuthAdapter
JSON 适配新模型GsonParserJsonParser(Kotlinx Serialization)GsonAdapter
推送服务的统一接口FCM、APNs、Huawei PushPushService token registerPushServiceAdapter

每个 Adapter 封装了转换逻辑:GsonAdapter 通过 @SerializedName 将 JSON 字符串转换为对象,PushServiceAdapter 抽象了不同平台的 token 注册。客户端(业务逻辑)使用统一接口,不依赖于具体实现。

实现 Adapter 时的常见错误

Adapter 实现中的错误会导致列表卡顿、内存泄漏和数据更新错误。我们来分析三个常见问题。

onBindViewHolder 中的重量级操作

onBindViewHolder 在滚动时为每个单元格调用。此方法中的对象创建、JSON 解析或数据库查询会导致列表丢帧(frame drops)。解决方案:提前执行所有重量级计算,将已准备好的数据传递给适配器。对于图片,使用带缓存的库——Glide、Coil、Kingfisher。

忽略 DiffUtil / DiffableDataSource

每次更新时调用 notifyDataSetChanged() 会重绘整个列表,导致闪烁和输入字段焦点丢失。解决方案:在 Android 中使用带 DiffUtil 的 ListAdapter,或在 iOS 中使用 DiffableDataSource。差异计算对于最多 1000 个元素的列表耗时不到 1 毫秒,并确保平滑动画。

在适配器中存储重量级对象

适配器的生命周期与 RecyclerView/UITableView 相同。在适配器中存储 Bitmap 或大型数组会导致配置更改(屏幕旋转、主题切换)时发生内存泄漏。解决方案:适配器应仅存储轻量级数据模型(data class / struct),并通过 ViewHolder 按需加载重量级资源。

常见问题

Adapter 和 Proxy 有什么区别?

Adapter 将 Adaptee 接口转换为 Target 接口——客户端获得新的交互方式。Proxy 提供与原始对象相同的接口,但添加了访问控制、缓存或懒初始化。Proxy 不更改接口,Adapter——更改。

为什么 RecyclerView.Adapter 是 Adapter 模式的示例?

RecyclerView.Adapter 将数据(例如 User 列表)转换为 RecyclerView 可以显示的 ViewHolder。RecyclerView 期望 ViewHolder,数据格式为 List<User>——Adapter 将一个适配到另一个。此外,Adapter 通过回收池管理 ViewHolder 的生命周期,提高了列表性能。

Object Adapter 和 Class Adapter 有什么区别?

Object Adapter 使用组合:包含对 Adaptee 的引用并委托调用给它们。Class Adapter 使用多重继承:继承 Adaptee 并实现 Target。在 Java、Kotlin 和 Swift 中,由于缺少类的多重继承,只有 Object Adapter 可用。Class Adapter 仅在 C++ 中可行。

何时使用 Adapter 而不是 Facade?

Adapter 用于当两个现有类需要通过转换其中一个的接口来协同工作时。Facade——当需要通过提供简单接口来简化与复杂子系统的交互时。Adapter——为兼容性,Facade——为简化。

可以在没有接口的情况下实现 Adapter 吗?

可以,通过闭包(closures)。可以传递一个函数而不是具有一个方法的接口。例如,在 Swift 中:let adapter = { (data: Data) -> [CellConfig] in /* 转换 */ }。这种方法称为函数式适配器。但对于 2 个以上的方法,协议/接口仍然是首选——它使契约明确。

总结

  • Adapter — 结构型模式,通过中间类将不兼容的 Adaptee 接口转换为 Target
  • RecyclerView.Adapter(Android)和 UITableViewDataSource(iOS)——该模式在移动开发中的主要示例
  • Object Adapter 使用组合;Class Adapter(仅 C++)——多重继承
  • ListAdapter + DiffUtil(Android)和 DiffableDataSource(iOS)——具有自动差异计算和动画的现代演进
  • onBindViewHolder 中的重量级操作、忽略 DiffUtil 和在适配器中存储资源——常见错误
  • Adapter 用于集成旧代码、适配格式和统一不同实现
  • 建议:使用 ListAdapter/DiffableDataSource 而不是手动管理列表更新

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读