一、基本概念
定义
适配器模式将一个类的接口转换成客户端所期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。
核心思想
- 接口转换:将一个接口转换为另一个客户端期望的接口
- 兼容性:让不兼容的接口能够协同工作
- 包装器:适配器本质是一个包装器
两种实现方式
- 类适配器:通过继承实现(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()
七、适配器模式的优缺点
✅ 优点
- 提高复用性:可以复用现有的类,无需修改其代码
- 提高灵活性:通过适配器可以灵活地使用不同的类
- 解耦:客户端与具体实现解耦
- 符合开闭原则:可以随时增加新的适配器,无需修改现有代码
- 统一接口:可以将多个不同的接口统一为一个接口
❌ 缺点
- 增加复杂性:增加了额外的类和对象
- 可能降低性能:适配器调用会增加一层间接调用
- 过度使用:如果系统中到处都是适配器,会变得难以理解
- 不是银弹:对于简单的接口转换,可能过度设计
八、与其他模式的对比
适配器模式 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 桥接模式
// 适配器模式:事后补救,让两个已有接口能一起工作
// 通常是在设计完成后的修改
// 桥接模式:事前设计,将抽象与实现分离
// 通常在系统设计时就考虑扩展性
九、最佳实践指南
何时使用适配器模式?
✅ 适用场景:
- 系统升级:使用新的类库替换旧的类库时
- 第三方集成:集成第三方库,但接口不兼容时
- 统一接口:多个类有相似功能但接口不同,需要统一接口时
- 数据转换:将一种数据格式转换为另一种格式时
- API版本适配:处理不同版本API的兼容性问题
❌ 避免使用场景:
- 接口本来就兼容,不需要适配
- 可以通过重构统一接口
- 系统设计初期,可以直接设计统一的接口
- 过度使用会导致系统复杂性增加
Android开发建议
- 优先使用对象适配器:比类适配器更灵活
- 合理使用RecyclerView.Adapter:这是Android中最常用的适配器
- 考虑使用Kotlin扩展函数:对于简单的适配,扩展函数更简洁
- 注意内存泄漏:适配器中持有Context或View引用时要小心
- 性能优化:对于列表适配器,使用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开发中最常用的设计模式之一,特别是在以下场景:
- 数据绑定:RecyclerView.Adapter是适配器模式的完美体现
- API兼容:处理不同Android版本的API差异
- 第三方集成:集成不同库的统一接口
- 架构优化:在不同层之间转换数据模型
关键点:
- 适配器是转换器:将一个接口转换为另一个接口
- 两种实现方式:对象适配器(推荐)和类适配器
- 不是装饰器:适配器改变接口,装饰器增强功能
- Android中无处不在:从RecyclerView到数据转换都在使用
在Android中的实际应用:
- RecyclerView.Adapter:将数据适配到视图
- PagerAdapter:将数据适配到ViewPager
- 资源适配:不同屏幕尺寸、语言、方向的资源适配
Kotlin优化:
- 扩展函数:简化简单适配
- 高阶函数:函数式适配器实现
适配器模式是解决接口不兼容问题的利器,合理使用可以显著提高代码的复用性和可维护性。在Android开发中,掌握适配器模式是每个开发者必备的技能