思想
本质是 把一个“接口不兼容”的对象,转换成你当前系统期望的接口,从而进行调用。
——将一个类的接口,转换成客户端期望的接口,使原本不兼容的类可以协同工作。
——在两个不兼容的接口之间架一座"桥",让原本无法协作的类可以一起工作,而不必修改原有代码。
可以理解为“中间翻译层”。
总结来说,适配器模式用于解决接口不兼容的问题,通过适配器将不兼容的接口转换为兼容的接口,从而使得不同的类能够一起工作。
三个角色
适配者:具有原始接口的类
抽象接口:客户端期望的接口形式
适配器:封装,持有适配者,转发调用
应用场景
常用于集成/调用 第三方 SDK、适配旧代码、数据模型适配。
“旧代码 / 第三方库 / 不同结构的数据 → 统一成我当前代码能用的样子”
应用1: 第三方 SDK的二次封装
你接入了一个第三方 SDK,它的接口不符合你当前项目的设计规范(比如命名、回调方式不同)。即使规范,原则上也不要直接依赖,直接调用其接口。
例
class ThirdPartyAudioSDK {
// 播放
func loadAndPlay(path: String, volume: Float) {
print("SDK 播放: \(path) 音量: \(volume)")
}
}
一般调用方式,直接依赖SDK类
class VC {
func do(){
let sdk = ThirdPartyAudioSDK()
sdk.loadAndPlay()
}
}
采用适配器模式,对SDK进行包装
1、设计抽象协议(接口)
protocol AudioPlayer {
func play(file: String)
}
2、设计适配器类
遵循协议,持有SDK对象。实质:调用转发
class AudioPlayerAdapter: AudioPlayer {
private let sdk = ThirdPartyAudioSDK()
func play(file: String) {
// 转换调用:适配接口差异
sdk.loadAndPlay(path: "/media/\(file)", volume: 1.0)
}
}
3、业务调用
class VC {
func playMusic(player: AudioPlayer) {
player.play(file: "song.mp3")
}
}
self.playMusic(player: AudioPlayerAdapter())
这样就实现了解耦,业务代码只依赖 AudioPlayer 协议,不知道内部用的是第三方 SDK。
好处:可灵活更换SDK;不污染业务代码。
应用2:服务器数据 的封装
服务端返回的JSON数据,最好不要直接读取使用。原因:
- 避免耦合
- 字段名不规范/适合
- 不是所有字段都显示在UI上
- 需要进行数据转换/预处理
{
"num": 1,
"is_expire": 0,
"reward_content": "10",
"reward_name": "WSpoint_reward",
"reward_type": 1,
"sign_state": 3,
"sign_time": 0,
"trigger_type": 1
}
解析
struct UserDTO: Decodable {
let sign_time: String
let trigger_type: Int
....
}
采用适配器模式,对服务端返回的数据进行封装
1、设计抽象协议
涵盖 业务层需要用到的数据,及 UI层可直接展示的数据
如:
protocol UIDataDisplay {
var count: Int { get }
var name: String { get }
.....
}
2、设计适配器类
class ModelAdater: UIDataDisplay {
let json: UserDTO
var num: Int
var type: Int
...
init(json: UserDTO){
self.num = json.num
...
}
// 实现抽象属性
var count: Int {
return json.num
}
var name: String {
return json.firstName + json.secondName
}
}
3、UI 与 具体数据类型 解耦
class Cell {
func config(model: UIDataDisplay){
self.nameLabel.text = model.name
...
}
}
好处
- 数据集中在Adapter进行处理
- 解耦 UI 和数据结构
- 扩展性,后端字段变了 → 只改 Adapter