Android 适配器模式浅析

50 阅读7分钟

一、基本概念

定义

适配器模式将一个类的接口转换成客户端所期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。

核心思想

  1. 接口转换:将一个接口转换为另一个客户端期望的接口
  2. 兼容性:让不兼容的接口能够协同工作
  3. 包装器:适配器本质是一个包装器

两种实现方式

  • 类适配器:通过继承实现(Kotlin/Java不支持多重继承,限制较多)
  • 对象适配器:通过组合实现(推荐使用)

二、基本结构

三个核心角色

// 1. Target (目标接口) - 客户端期望的接口
interface MediaPlayer {
    fun play(audioType: String, fileName: String)
}

// 2. Adaptee (适配者) - 需要被适配的现有类
class AdvancedMediaPlayer {
    fun playVlc(fileName: String) {
        println("播放VLC文件: $fileName")
    }
    
    fun playMp4(fileName: String) {
        println("播放MP4文件: $fileName")
    }
    
    fun playAvi(fileName: String) {
        println("播放AVI文件: $fileName")
    }
}

// 3. Adapter (适配器) - 将Adaptee适配到Target
class MediaAdapter : MediaPlayer {
    private val advancedPlayer = AdvancedMediaPlayer()
    
    override fun play(audioType: String, fileName: String) {
        when (audioType.toLowerCase()) {
            "vlc" -> advancedPlayer.playVlc(fileName)
            "mp4" -> advancedPlayer.playMp4(fileName)
            "avi" -> advancedPlayer.playAvi(fileName)
            else -> println("不支持的格式: $audioType")
        }
    }
}

// 客户端使用
fun main() {
    val player: MediaPlayer = MediaAdapter()
    player.play("mp4", "movie.mp4")
    player.play("vlc", "music.vlc")
    player.play("avi", "video.avi")
    player.play("mp3", "song.mp3") // 不支持的格式
}

三、Android中的适配器模式

1. RecyclerView.Adapter - 最经典的适配器模式

// 数据模型
data class User(
    val id: Long,
    val name: String,
    val email: String,
    val avatarUrl: String
)

// ViewHolder
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    private val nameTextView: TextView = view.findViewById(R.id.tv_name)
    private val emailTextView: TextView = view.findViewById(R.id.tv_email)
    private val avatarImageView: ImageView = view.findViewById(R.id.iv_avatar)
    
    fun bind(user: User) {
        nameTextView.text = user.name
        emailTextView.text = user.email
        Glide.with(avatarImageView.context)
            .load(user.avatarUrl)
            .placeholder(R.drawable.avatar_placeholder)
            .into(avatarImageView)
    }
}

// 适配器 - 将List<User>适配到RecyclerView
class UserAdapter(private val users: List<User>) : 
    RecyclerView.Adapter<UserViewHolder>() {
    
    // 将数据项转换为View
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }
    
    // 将数据绑定到View
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = users[position]
        holder.bind(user)
    }
    
    // 返回数据项数量
    override fun getItemCount(): Int = users.size
    
    // 可选:多类型视图适配
    override fun getItemViewType(position: Int): Int {
        return if (users[position].id % 2 == 0L) VIEW_TYPE_EVEN else VIEW_TYPE_ODD
    }
    
    companion object {
        private const val VIEW_TYPE_EVEN = 0
        private const val VIEW_TYPE_ODD = 1
    }
}

// 使用
val users = listOf(
    User(1, "张三", "zhangsan@example.com", "https://example.com/avatar1.jpg"),
    User(2, "李四", "lisi@example.com", "https://example.com/avatar2.jpg")
)

val adapter = UserAdapter(users)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(context)

2. PagerAdapter (ViewPager适配器)

class ImagePagerAdapter(
    private val context: Context,
    private val imageUrls: List<String>
) : PagerAdapter() {
    
    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val imageView = ImageView(context).apply {
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            scaleType = ImageView.ScaleType.CENTER_CROP
        }
        
        Glide.with(context)
            .load(imageUrls[position])
            .into(imageView)
        
        container.addView(imageView)
        return imageView
    }
    
    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
        container.removeView(`object` as View)
    }
    
    override fun getCount(): Int = imageUrls.size
    
    override fun isViewFromObject(view: View, `object`: Any): Boolean {
        return view == `object`
    }
}

四、适配器模式的变体

1. 双向适配器

// 两个不兼容的接口
interface EuropeanPlug {
    fun useTwoPins()
}

interface ChineseSocket {
    fun acceptThreePins()
}

// 欧洲插头实现
class EuropeanPlugImpl : EuropeanPlug {
    override fun useTwoPins() {
        println("使用两脚插头")
    }
}

// 中国插座实现
class ChineseSocketImpl : ChineseSocket {
    override fun acceptThreePins() {
        println("接受三脚插头")
    }
}

// 双向适配器
class PlugSocketAdapter(
    private val plug: EuropeanPlug,
    private val socket: ChineseSocket
) : EuropeanPlug by plug, ChineseSocket by socket {
    
    // 添加额外的转换逻辑
    fun connect() {
        println("适配器开始工作...")
        useTwoPins()
        println("转换为三脚接口...")
        acceptThreePins()
        println("连接成功!")
    }
}

// 使用
val plug = EuropeanPlugImpl()
val socket = ChineseSocketImpl()
val adapter = PlugSocketAdapter(plug, socket)
adapter.connect()

2. 类适配器 (通过接口继承实现)

// 目标接口
interface NewSystem {
    fun newRequest(): String
}

// 被适配的类
class OldSystem {
    fun oldRequest(): String {
        return "来自旧系统的数据"
    }
}

// 类适配器(Kotlin中通过委托实现类似效果)
class ClassAdapter(private val oldSystem: OldSystem) : NewSystem {
    override fun newRequest(): String {
        // 调用旧系统的方法并适配
        val oldResult = oldSystem.oldRequest()
        return "适配后的数据: $oldResult"
    }
}

3. 默认适配器 (Adapter接口的空实现)

// 接口有多个方法,但通常只用到少数几个
interface OnClickListener {
    fun onClick(view: View)
    fun onLongClick(view: View): Boolean
    fun onDoubleClick(view: View)
}

// 默认适配器(提供空实现)
open class DefaultOnClickListener : OnClickListener {
    override fun onClick(view: View) {
        // 空实现
    }
    
    override fun onLongClick(view: View): Boolean {
        // 空实现
        return false
    }
    
    override fun onDoubleClick(view: View) {
        // 空实现
    }
}

// 使用时只需重写需要的方法
button.setOnClickListener(object : DefaultOnClickListener() {
    override fun onClick(view: View) {
        Toast.makeText(view.context, "点击", Toast.LENGTH_SHORT).show()
    }
})

五、Android开发中的实际应用

1. 数据源适配器

// 将不同数据源适配为统一接口
interface DataSource {
    fun getData(): List<Any>
    fun getCount(): Int
}

// 数据库数据源
class DatabaseDataSource(private val context: Context) : DataSource {
    private val databaseHelper = DatabaseHelper(context)
    
    override fun getData(): List<Any> {
        return databaseHelper.getAllUsers()
    }
    
    override fun getCount(): Int {
        return databaseHelper.getUserCount()
    }
}

// 网络数据源
class NetworkDataSource(private val apiService: ApiService) : DataSource {
    private var cachedData: List<Any> = emptyList()
    
    override fun getData(): List<Any> {
        return try {
            val response = apiService.getUsers().execute()
            if (response.isSuccessful) {
                cachedData = response.body() ?: emptyList()
                cachedData
            } else {
                emptyList()
            }
        } catch (e: Exception) {
            emptyList()
        }
    }
    
    override fun getCount(): Int {
        return cachedData.size
    }
}

// 本地文件数据源
class FileDataSource(private val context: Context, private val fileName: String) : DataSource {
    override fun getData(): List<Any> {
        return try {
            val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
            val type = object : TypeToken<List<User>>() {}.type
            Gson().fromJson(json, type)
        } catch (e: Exception) {
            emptyList()
        }
    }
    
    override fun getCount(): Int {
        return getData().size
    }
}

// 统一适配器
class UniversalAdapter(private val dataSource: DataSource) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        // 根据数据类型创建不同的ViewHolder
        return when (viewType) {
            TYPE_USER -> UserViewHolder(inflateView(parent, R.layout.item_user))
            TYPE_PRODUCT -> ProductViewHolder(inflateView(parent, R.layout.item_product))
            else -> throw IllegalArgumentException("未知的视图类型")
        }
    }
    
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = dataSource.getData()[position]
        when (holder) {
            is UserViewHolder -> holder.bind(item as User)
            is ProductViewHolder -> holder.bind(item as Product)
        }
    }
    
    override fun getItemCount(): Int = dataSource.getCount()
    
    override fun getItemViewType(position: Int): Int {
        val item = dataSource.getData()[position]
        return when (item) {
            is User -> TYPE_USER
            is Product -> TYPE_PRODUCT
            else -> TYPE_UNKNOWN
        }
    }
    
    private fun inflateView(parent: ViewGroup, layoutId: Int): View {
        return LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
    }
    
    companion object {
        private const val TYPE_USER = 0
        private const val TYPE_PRODUCT = 1
        private const val TYPE_UNKNOWN = -1
    }
}

2. API版本适配器

// 不同API版本的相机功能适配
interface CameraSystem {
    fun takePhoto()
    fun recordVideo()
    fun switchCamera()
}

// Android 5.0+ 的相机实现
class ModernCamera(private val context: Context) : CameraSystem {
    private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
    
    override fun takePhoto() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            cameraManager.openCamera("0", object : CameraDevice.StateCallback() {
                override fun onOpened(camera: CameraDevice) {
                    // 现代API拍照逻辑
                    println("使用现代API拍照")
                }
                override fun onDisconnected(camera: CameraDevice) {}
                override fun onError(camera: CameraDevice, error: Int) {}
            }, null)
        }
    }
    
    override fun recordVideo() {
        println("使用现代API录制视频")
    }
    
    override fun switchCamera() {
        println("使用现代API切换相机")
    }
}

// Android 5.0 以下的相机实现
class LegacyCamera : CameraSystem {
    private lateinit var camera: android.hardware.Camera
    
    override fun takePhoto() {
        camera = android.hardware.Camera.open()
        // 传统API拍照逻辑
        println("使用传统API拍照")
        camera.release()
    }
    
    override fun recordVideo() {
        println("使用传统API录制视频")
    }
    
    override fun switchCamera() {
        println("使用传统API切换相机")
    }
}

// 相机适配器 - 根据API版本选择实现
class CameraAdapter(private val context: Context) : CameraSystem {
    private val cameraSystem: CameraSystem by lazy {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            ModernCamera(context)
        } else {
            LegacyCamera()
        }
    }
    
    override fun takePhoto() {
        cameraSystem.takePhoto()
    }
    
    override fun recordVideo() {
        cameraSystem.recordVideo()
    }
    
    override fun switchCamera() {
        cameraSystem.switchCamera()
    }
}

// 使用 - 无需关心API版本
val camera = CameraAdapter(context)
camera.takePhoto()

3. 第三方库适配器

// 图片加载库适配器
interface ImageLoader {
    fun load(url: String, imageView: ImageView)
    fun load(url: String, imageView: ImageView, placeholder: Int)
    fun load(url: String, imageView: ImageView, placeholder: Int, error: Int)
    fun preload(url: String)
    fun clearCache()
}

// Glide适配器
class GlideImageLoader(private val context: Context) : ImageLoader {
    override fun load(url: String, imageView: ImageView) {
        Glide.with(context)
            .load(url)
            .into(imageView)
    }
    
    override fun load(url: String, imageView: ImageView, placeholder: Int) {
        Glide.with(context)
            .load(url)
            .placeholder(placeholder)
            .into(imageView)
    }
    
    override fun load(url: String, imageView: ImageView, placeholder: Int, error: Int) {
        Glide.with(context)
            .load(url)
            .placeholder(placeholder)
            .error(error)
            .into(imageView)
    }
    
    override fun preload(url: String) {
        Glide.with(context)
            .load(url)
            .preload()
    }
    
    override fun clearCache() {
        Glide.get(context).clearMemory()
        GlobalScope.launch(Dispatchers.IO) {
            Glide.get(context).clearDiskCache()
        }
    }
}

// Picasso适配器
class PicassoImageLoader(private val context: Context) : ImageLoader {
    override fun load(url: String, imageView: ImageView) {
        Picasso.get()
            .load(url)
            .into(imageView)
    }
    
    override fun load(url: String, imageView: ImageView, placeholder: Int) {
        Picasso.get()
            .load(url)
            .placeholder(placeholder)
            .into(imageView)
    }
    
    override fun load(url: String, imageView: ImageView, placeholder: Int, error: Int) {
        Picasso.get()
            .load(url)
            .placeholder(placeholder)
            .error(error)
            .into(imageView)
    }
    
    override fun preload(url: String) {
        Picasso.get()
            .load(url)
            .fetch()
    }
    
    override fun clearCache() {
        Picasso.get()
            .invalidate(url)
    }
}

// 统一图片加载器工厂
object ImageLoaderFactory {
    private var currentLoader: ImageLoader? = null
    
    fun getImageLoader(context: Context): ImageLoader {
        if (currentLoader == null) {
            // 根据配置或环境选择加载器
            currentLoader = if (useGlide()) {
                GlideImageLoader(context.applicationContext)
            } else {
                PicassoImageLoader(context.applicationContext)
            }
        }
        return currentLoader!!
    }
    
    fun switchLoader(context: Context, useGlide: Boolean) {
        currentLoader = if (useGlide) {
            GlideImageLoader(context.applicationContext)
        } else {
            PicassoImageLoader(context.applicationContext)
        }
    }
    
    private fun useGlide(): Boolean {
        // 根据配置决定使用哪个库
        return true // 默认使用Glide
    }
}

// 使用
val imageLoader = ImageLoaderFactory.getImageLoader(context)
imageLoader.load("https://example.com/image.jpg", imageView, R.drawable.placeholder)

六、Kotlin中的适配器模式优化

1. 使用扩展函数作为适配器

// 现有类
class LegacyUser(val firstName: String, val lastName: String, val age: Int)

// 新接口
interface ModernUser {
    val fullName: String
    val age: Int
    val isAdult: Boolean
}

// 使用扩展函数适配
fun LegacyUser.toModernUser(): ModernUser {
    return object : ModernUser {
        override val fullName: String
            get() = "$firstName $lastName"
        override val age: Int
            get() = this@toModernUser.age
        override val isAdult: Boolean
            get() = age >= 18
    }
}

// 使用
val legacyUser = LegacyUser("张", "三", 25)
val modernUser: ModernUser = legacyUser.toModernUser()
println("${modernUser.fullName} 是成年人吗?${modernUser.isAdult}")

2. 使用高阶函数简化适配器

// 函数式适配器
class FunctionalAdapter<T, R>(
    private val data: List<T>,
    private val transform: (T) -> R
) {
    fun getAdaptedData(): List<R> {
        return data.map(transform)
    }
}

// 使用
data class ApiUser(val id: Int, val username: String, val createdAt: String)
data class DomainUser(val id: Int, val name: String, val joinDate: Date)

val apiUsers = listOf(
    ApiUser(1, "user1", "2023-01-01"),
    ApiUser(2, "user2", "2023-02-01")
)

val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())

val adapter = FunctionalAdapter(apiUsers) { apiUser ->
    DomainUser(
        id = apiUser.id,
        name = apiUser.username,
        joinDate = dateFormat.parse(apiUser.createdAt) ?: Date()
    )
}

val domainUsers = adapter.getAdaptedData()

七、适配器模式的优缺点

✅ 优点

  1. 提高复用性:可以复用现有的类,无需修改其代码
  2. 提高灵活性:通过适配器可以灵活地使用不同的类
  3. 解耦:客户端与具体实现解耦
  4. 符合开闭原则:可以随时增加新的适配器,无需修改现有代码
  5. 统一接口:可以将多个不同的接口统一为一个接口

❌ 缺点

  1. 增加复杂性:增加了额外的类和对象
  2. 可能降低性能:适配器调用会增加一层间接调用
  3. 过度使用:如果系统中到处都是适配器,会变得难以理解
  4. 不是银弹:对于简单的接口转换,可能过度设计

八、与其他模式的对比

适配器模式 vs 装饰器模式

// 适配器模式:转换接口,让不兼容的接口能一起工作
class Adapter(oldSystem: OldSystem) : NewSystem {
    override fun newMethod() {
        oldSystem.oldMethod() // 只是调用,不增强功能
    }
}

// 装饰器模式:增强功能,不改变接口
class Decorator(system: System) : System by system {
    override fun method() {
        println("开始执行")
        super.method() // 调用原有方法并增强
        println("执行结束")
    }
}

适配器模式 vs 外观模式

// 适配器模式:主要解决接口不兼容问题
class MediaAdapter : MediaPlayer {
    private val advancedPlayer = AdvancedMediaPlayer()
    
    override fun play(type: String, file: String) {
        when (type) {
            "mp4" -> advancedPlayer.playMp4(file) // 一对一转换
        }
    }
}

// 外观模式:简化复杂系统的接口
class HomeTheaterFacade {
    private val dvd = DvdPlayer()
    private val projector = Projector()
    private val lights = Lights()
    
    fun watchMovie(movie: String) {
        lights.dim(10)
        projector.on()
        projector.setInput("DVD")
        dvd.on()
        dvd.play(movie) // 简化多个系统的调用
    }
}

适配器模式 vs 桥接模式

// 适配器模式:事后补救,让两个已有接口能一起工作
// 通常是在设计完成后的修改

// 桥接模式:事前设计,将抽象与实现分离
// 通常在系统设计时就考虑扩展性

九、最佳实践指南

何时使用适配器模式?

✅ 适用场景

  1. 系统升级:使用新的类库替换旧的类库时
  2. 第三方集成:集成第三方库,但接口不兼容时
  3. 统一接口:多个类有相似功能但接口不同,需要统一接口时
  4. 数据转换:将一种数据格式转换为另一种格式时
  5. API版本适配:处理不同版本API的兼容性问题

❌ 避免使用场景

  1. 接口本来就兼容,不需要适配
  2. 可以通过重构统一接口
  3. 系统设计初期,可以直接设计统一的接口
  4. 过度使用会导致系统复杂性增加

Android开发建议

  1. 优先使用对象适配器:比类适配器更灵活
  2. 合理使用RecyclerView.Adapter:这是Android中最常用的适配器
  3. 考虑使用Kotlin扩展函数:对于简单的适配,扩展函数更简洁
  4. 注意内存泄漏:适配器中持有Context或View引用时要小心
  5. 性能优化:对于列表适配器,使用ViewHolder模式优化性能

实现技巧

// 1. 使用泛型增加灵活性
interface Adapter<in T, out R> {
    fun adapt(source: T): R
}

// 2. 支持链式适配
class ChainAdapter<T, R, S>(
    private val adapter1: Adapter<T, R>,
    private val adapter2: Adapter<R, S>
) : Adapter<T, S> {
    override fun adapt(source: T): S {
        return adapter2.adapt(adapter1.adapt(source))
    }
}

// 3. 可配置的适配器
class ConfigurableAdapter<T>(
    private val rules: Map<String, (T) -> Any>
) {
    fun adapt(source: T, config: Map<String, Any>): Map<String, Any> {
        return rules.mapValues { (key, transform) ->
            if (config.containsKey(key)) config[key] else transform(source)
        }
    }
}

十、总结

适配器模式是Android开发中最常用的设计模式之一,特别是在以下场景:

  1. 数据绑定:RecyclerView.Adapter是适配器模式的完美体现
  2. API兼容:处理不同Android版本的API差异
  3. 第三方集成:集成不同库的统一接口
  4. 架构优化:在不同层之间转换数据模型

关键点

  • 适配器是转换器:将一个接口转换为另一个接口
  • 两种实现方式:对象适配器(推荐)和类适配器
  • 不是装饰器:适配器改变接口,装饰器增强功能
  • Android中无处不在:从RecyclerView到数据转换都在使用

在Android中的实际应用

  • RecyclerView.Adapter:将数据适配到视图
  • PagerAdapter:将数据适配到ViewPager
  • 资源适配:不同屏幕尺寸、语言、方向的资源适配

Kotlin优化

  • 扩展函数:简化简单适配
  • 高阶函数:函数式适配器实现

适配器模式是解决接口不兼容问题的利器,合理使用可以显著提高代码的复用性和可维护性。在Android开发中,掌握适配器模式是每个开发者必备的技能