适配器模式 (Adapter Pattern)
意图
适配器模式是一种结构型设计模式,通过将一个类的接口转换成客户希望的另一个接口,使得原本接口不兼容的类可以一起工作。适配器模式主要用于解决两个现有接口不兼容的问题。
问题
在现实世界中,考虑一个旧的音频播放系统只能播放MP3格式的音频文件,而新的音频播放器可以播放多种格式(例如MP4、FLAC等)。如果想在旧的系统上使用新的播放器,就需要一个适配器来转换新的播放器接口,使其与旧系统兼容。
解决方案
使用适配器模式,我们可以创建一个适配器类,实现旧系统所期望的接口,并在适配器内部调用新的播放器接口,从而使得旧系统可以使用新的播放器。
模式结构
- 目标接口(Target):定义客户端使用的接口。
- 适配者(Adaptee):定义已经存在且需要适配的接口。
- 适配器(Adapter):实现目标接口,并调用适配者接口的方法,以实现接口的转换。
- 客户端(Client):使用目标接口与适配器交互。
代码
以下是使用Go语言实现的适配器模式示例:
package main
import "fmt"
// 目标接口 - 旧系统期望的接口
type MediaPlayer interface {
Play(fileType, fileName string)
}
// 适配者 - 新的音频播放器,可以播放多种格式
type AdvancedMediaPlayer interface {
PlayMP4(fileName string)
PlayFLAC(fileName string)
}
// 具体适配者 - MP4播放器
type MP4Player struct{}
func (p *MP4Player) PlayMP4(fileName string) {
fmt.Println("Playing MP4 file. Name:", fileName)
}
func (p *MP4Player) PlayFLAC(fileName string) {
// MP4Player 不支持播放 FLAC 文件
}
// 具体适配者 - FLAC播放器
type FLACPlayer struct{}
func (p *FLACPlayer) PlayMP4(fileName string) {
// FLACPlayer 不支持播放 MP4 文件
}
func (p *FLACPlayer) PlayFLAC(fileName string) {
fmt.Println("Playing FLAC file. Name:", fileName)
}
// 适配器 - 适配新的音频播放器到旧系统
type MediaAdapter struct {
advancedPlayer AdvancedMediaPlayer
}
func NewMediaAdapter(fileType string) *MediaAdapter {
if fileType == "mp4" {
return &MediaAdapter{&MP4Player{}}
} else if fileType == "flac" {
return &MediaAdapter{&FLACPlayer{}}
}
return nil
}
func (a *MediaAdapter) Play(fileType, fileName string) {
if fileType == "mp4" {
a.advancedPlayer.PlayMP4(fileName)
} else if fileType == "flac" {
a.advancedPlayer.PlayFLAC(fileName)
}
}
// 具体目标 - 旧的音频播放器,只能播放 MP3 文件
type AudioPlayer struct {
adapter *MediaAdapter
}
func (p *AudioPlayer) Play(fileType, fileName string) {
if fileType == "mp3" {
fmt.Println("Playing MP3 file. Name:", fileName)
} else if fileType == "mp4" || fileType == "flac" {
p.adapter = NewMediaAdapter(fileType)
p.adapter.Play(fileType, fileName)
} else {
fmt.Println("Invalid media. ", fileType, " format not supported")
}
}
func main() {
audioPlayer := &AudioPlayer{}
audioPlayer.Play("mp3", "song.mp3")
audioPlayer.Play("mp4", "movie.mp4")
audioPlayer.Play("flac", "audio.flac")
audioPlayer.Play("avi", "video.avi")
}
适用场景
- 系统需要使用现有的类,但其接口不符合系统的需求。
- 创建一个可复用的类,用于与不兼容接口的其他类一起工作。
- 需要转换多个现有类接口成为一个统一接口的场景。
实现方式
- 定义目标接口,声明客户端所期待的接口。
- 创建适配者类,实现现有的接口。
- 创建适配器类,实现目标接口,并在适配器类内部调用适配者的接口方法。
- 在客户端代码中,通过目标接口与适配器交互。
优缺点
优点:
- 分离了客户端代码与不兼容接口的实现细节,增加了代码的可复用性和灵活性。
- 符合开闭原则,增加新的适配器不会影响现有代码。
- 可以提高现有类的复用性。
缺点:
- 增加了系统的复杂性,需要定义额外的适配器类。
- 适配器模式的使用可能会导致过多的适配器类,从而使得系统更加复杂和难以维护。
其他模式的关系
- 装饰器模式(Decorator Pattern):装饰器模式用于增强对象的功能,而适配器模式用于改变对象的接口。
- 桥接模式(Bridge Pattern):桥接模式分离抽象部分和实现部分,使它们可以独立变化。适配器模式用于将现有类的接口转换为客户希望的接口。
- 外观模式(Facade Pattern):外观模式为子系统中的一组接口提供一个统一的接口,适配器模式则是使不兼容的接口能够协同工作。