Adapter — 是一种结构型设计模式,它将一个类的接口转换成客户端期望的另一个接口。在移动开发中,Adapter 最常用于 Android 的 RecyclerView.Adapter 和 iOS 的 UITableViewDataSource。根据 Google I/O(2024)的数据,95% 的 Android 应用使用 RecyclerView 来显示列表,每个应用都需要自己的 Adapter 实现。
要点
Adapter — 结构型模式,解决接口不兼容的问题。它将一个对象(Adaptee)包装到具有客户端期望接口(Target)的类(Adapter)中。客户端与 Target 一起工作,不知道 Adaptee 的存在。这是 Wrapper 模式的一个变体。
// 具有不兼容接口的现有类
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。这允许在不更改客户端代码的情况下替换实现。
RecyclerView.Adapter — 是 Android 中最常见的 Adapter 模式实现。它将数据(对象列表)转换为 RecyclerView 在屏幕上显示的 ViewHolder。随着 ListAdapter(Android Architecture Components)的出现,该模式获得了内置的差异计算支持,用于动画展示变化。
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 的关键优势。
UITableViewDataSource — 既是 Adapter 模式,也是 DataSource 模式。它将模型数据转换为表格单元格。从 iOS 13 开始,Apple 引入了 DiffableDataSource——一个现代替代品,类似于 Android 中的 ListAdapter,自动计算变化并动画展示更新。
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 使用组合:适配器包含对 Adaptee 对象的引用并委托调用给它。Class Adapter 使用继承:适配器继承 Adaptee 并同时实现目标接口。在 Java、Swift 和 Kotlin 中,由于缺少多重继承,Class Adapter 不可行——只剩下 Object Adapter。
| 特性 | Object Adapter | Class Adapter |
|---|---|---|
| 机制 | 组合(包含 Adaptee) | 继承(extends Adaptee) |
| 灵活性 | 更高——Adaptee 可在运行时更改 | 更低——Adaptee 在编译时固定 |
| 耦合度 | 弱(通过接口) | 强(通过继承) |
| 子类适配 | 是——Adaptee 可以是任何子类 | 否——仅具体超类 |
| 可用性 | Java, Kotlin, Swift, C++, C# | 仅支持多重继承的语言(C++) |
在移动开发中,仅使用 Object Adapter。RecyclerView.Adapter 包含对数据和 LayoutInflater 的引用,UITableViewDataSource 包含元素数组。与继承相比,组合使代码更灵活、更可测试。
Adapter 不仅用于列表。让我们看看移动开发实践中的三个场景,其中该模式解决了不兼容组件集成的问题。
| 场景 | Adaptee | Target | Adapter |
|---|---|---|---|
| 集成旧的身份验证库 | LegacyAuthLib(基于回调) | AuthService(suspend) | LegacyAuthAdapter |
| JSON 适配新模型 | GsonParser | JsonParser(Kotlinx Serialization) | GsonAdapter |
| 推送服务的统一接口 | FCM、APNs、Huawei Push | PushService token register | PushServiceAdapter |
每个 Adapter 封装了转换逻辑:GsonAdapter 通过 @SerializedName 将 JSON 字符串转换为对象,PushServiceAdapter 抽象了不同平台的 token 注册。客户端(业务逻辑)使用统一接口,不依赖于具体实现。
Adapter 实现中的错误会导致列表卡顿、内存泄漏和数据更新错误。我们来分析三个常见问题。
onBindViewHolder 在滚动时为每个单元格调用。此方法中的对象创建、JSON 解析或数据库查询会导致列表丢帧(frame drops)。解决方案:提前执行所有重量级计算,将已准备好的数据传递给适配器。对于图片,使用带缓存的库——Glide、Coil、Kingfisher。
每次更新时调用 notifyDataSetChanged() 会重绘整个列表,导致闪烁和输入字段焦点丢失。解决方案:在 Android 中使用带 DiffUtil 的 ListAdapter,或在 iOS 中使用 DiffableDataSource。差异计算对于最多 1000 个元素的列表耗时不到 1 毫秒,并确保平滑动画。
适配器的生命周期与 RecyclerView/UITableView 相同。在适配器中存储 Bitmap 或大型数组会导致配置更改(屏幕旋转、主题切换)时发生内存泄漏。解决方案:适配器应仅存储轻量级数据模型(data class / struct),并通过 ViewHolder 按需加载重量级资源。
常见问题
Adapter 将 Adaptee 接口转换为 Target 接口——客户端获得新的交互方式。Proxy 提供与原始对象相同的接口,但添加了访问控制、缓存或懒初始化。Proxy 不更改接口,Adapter——更改。
RecyclerView.Adapter 将数据(例如 User 列表)转换为 RecyclerView 可以显示的 ViewHolder。RecyclerView 期望 ViewHolder,数据格式为 List<User>——Adapter 将一个适配到另一个。此外,Adapter 通过回收池管理 ViewHolder 的生命周期,提高了列表性能。
Object Adapter 使用组合:包含对 Adaptee 的引用并委托调用给它们。Class Adapter 使用多重继承:继承 Adaptee 并实现 Target。在 Java、Kotlin 和 Swift 中,由于缺少类的多重继承,只有 Object Adapter 可用。Class Adapter 仅在 C++ 中可行。
Adapter 用于当两个现有类需要通过转换其中一个的接口来协同工作时。Facade——当需要通过提供简单接口来简化与复杂子系统的交互时。Adapter——为兼容性,Facade——为简化。
可以,通过闭包(closures)。可以传递一个函数而不是具有一个方法的接口。例如,在 Swift 中:let adapter = { (data: Data) -> [CellConfig] in /* 转换 */ }。这种方法称为函数式适配器。但对于 2 个以上的方法,协议/接口仍然是首选——它使契约明确。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。