适配器模式 (Adapter Pattern)

适配器模式 (Adapter Pattern)

概述

适配器模式是一种结构型设计模式,它将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

意图

  • 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

适用场景

  • 需要使用现有的类,而它的接口不符合需要的接口
  • 想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作
  • 需要使用几个现有的子类,但通过对每个子类进行子类化来适配它们的接口不现实

结构

适配器模式有两种实现方式:类适配器(使用继承)和对象适配器(使用组合)。

类适配器结构

复制代码
┌─────────────┐          ┌─────────────┐
│   Client    │──────────>│   Target    │
├─────────────┤          ├─────────────┤
│             │          │ + request() │
└─────────────┘          └─────────────┘
                                 ▲
                                 │
┌─────────────┐          ┌─────────────┐
│  Adaptee    │<─────────│  Adapter    │
├─────────────┤          ├─────────────┤
│ + specificRequest()│    │ + request() │
└─────────────┘          └─────────────┘

对象适配器结构

复制代码
┌─────────────┐          ┌─────────────┐
│   Client    │──────────>│   Target    │
├─────────────┤          ├─────────────┤
│             │          │ + request() │
└─────────────┘          └─────────────┘
                                 ▲
                                 │
┌─────────────┐          ┌─────────────┐
│  Adaptee    │          │  Adapter    │
├─────────────┤          ├─────────────┤
│ + specificRequest()│    │ - adaptee   │
└─────────────┘          │ + request() │
                         └─────────────┘

参与者

  • Target:定义客户使用的与特定领域相关的接口
  • Client:与符合Target接口的对象协同工作
  • Adaptee:定义一个已经存在的接口,这个接口需要适配
  • Adapter:对Adaptee的接口与Target接口进行适配

示例代码

对象适配器示例

下面是一个完整的对象适配器模式示例,以电源适配器为例:

java 复制代码
// Target - 目标接口
public interface PowerSupply {
    void provide5V();
}

// Adaptee - 被适配者
public class PowerSocket {
    public void provide220V() {
        System.out.println("提供220V交流电");
    }
}

// Adapter - 适配器
public class PowerAdapter implements PowerSupply {
    private PowerSocket powerSocket;
    
    public PowerAdapter(PowerSocket powerSocket) {
        this.powerSocket = powerSocket;
    }
    
    @Override
    public void provide5V() {
        System.out.println("电源适配器开始工作...");
        powerSocket.provide220V();
        System.out.println("将220V交流电转换为5V直流电");
        System.out.println("提供5V直流电");
    }
}

// Client - 客户端
public class MobilePhone {
    private PowerSupply powerSupply;
    
    public MobilePhone(PowerSupply powerSupply) {
        this.powerSupply = powerSupply;
    }
    
    public void charge() {
        System.out.println("手机开始充电...");
        powerSupply.provide5V();
        System.out.println("手机充电完成");
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建电源插座(220V)
        PowerSocket powerSocket = new PowerSocket();
        
        // 创建电源适配器
        PowerAdapter powerAdapter = new PowerAdapter(powerSocket);
        
        // 创建手机并充电
        MobilePhone mobilePhone = new MobilePhone(powerAdapter);
        mobilePhone.charge();
    }
}

类适配器示例

java 复制代码
// Target - 目标接口
public interface PowerSupply {
    void provide5V();
}

// Adaptee - 被适配者
public class PowerSocket {
    public void provide220V() {
        System.out.println("提供220V交流电");
    }
}

// Adapter - 适配器(类适配器,使用继承)
public class PowerClassAdapter extends PowerSocket implements PowerSupply {
    @Override
    public void provide5V() {
        System.out.println("电源适配器开始工作...");
        provide220V();
        System.out.println("将220V交流电转换为5V直流电");
        System.out.println("提供5V直流电");
    }
}

// Client - 客户端
public class MobilePhone {
    private PowerSupply powerSupply;
    
    public MobilePhone(PowerSupply powerSupply) {
        this.powerSupply = powerSupply;
    }
    
    public void charge() {
        System.out.println("手机开始充电...");
        powerSupply.provide5V();
        System.out.println("手机充电完成");
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建电源适配器(类适配器)
        PowerClassAdapter powerAdapter = new PowerClassAdapter();
        
        // 创建手机并充电
        MobilePhone mobilePhone = new MobilePhone(powerAdapter);
        mobilePhone.charge();
    }
}

另一个示例 - 音频播放器

java 复制代码
// Target - 目标接口
public interface MediaPlayer {
    void play(String audioType, String fileName);
}

// Adaptee - 被适配者
public class AdvancedMediaPlayer {
    public void playVlc(String fileName) {
        System.out.println("播放VLC文件: " + fileName);
    }
    
    public void playMp4(String fileName) {
        System.out.println("播放MP4文件: " + fileName);
    }
}

// Adapter - 适配器
public class MediaAdapter implements MediaPlayer {
    private AdvancedMediaPlayer advancedMediaPlayer;
    
    public MediaAdapter(String audioType) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedMediaPlayer = new AdvancedMediaPlayer();
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedMediaPlayer = new AdvancedMediaPlayer();
        }
    }
    
    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedMediaPlayer.playVlc(fileName);
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedMediaPlayer.playMp4(fileName);
        }
    }
}

// ConcreteTarget - 具体目标
public class AudioPlayer implements MediaPlayer {
    private MediaAdapter mediaAdapter;
    
    @Override
    public void play(String audioType, String fileName) {
        // 播放MP3音乐文件的内置支持
        if (audioType.equalsIgnoreCase("mp3")) {
            System.out.println("播放MP3文件: " + fileName);
        }
        // mediaAdapter提供了对其他文件格式的支持
        else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            mediaAdapter = new MediaAdapter(audioType);
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("无效的媒体格式: " + audioType);
        }
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        AudioPlayer audioPlayer = new AudioPlayer();
        
        audioPlayer.play("mp3", "beyond the horizon.mp3");
        audioPlayer.play("mp4", "alone.mp4");
        audioPlayer.play("vlc", "far far away.vlc");
        audioPlayer.play("avi", "mind me.avi");
    }
}

双向适配器

java 复制代码
// 两个需要相互适配的接口
public interface AmericanSocket {
    void provide110V();
}

public interface EuropeanSocket {
    void provide220V();
}

// 两个具体实现
public class AmericanSocketImpl implements AmericanSocket {
    @Override
    public void provide110V() {
        System.out.println("提供110V电压");
    }
}

public class EuropeanSocketImpl implements EuropeanSocket {
    @Override
    public void provide220V() {
        System.out.println("提供220V电压");
    }
}

// 双向适配器
public class SocketAdapter implements AmericanSocket, EuropeanSocket {
    private AmericanSocket americanSocket;
    private EuropeanSocket europeanSocket;
    
    public SocketAdapter(AmericanSocket americanSocket) {
        this.americanSocket = americanSocket;
    }
    
    public SocketAdapter(EuropeanSocket europeanSocket) {
        this.europeanSocket = europeanSocket;
    }
    
    @Override
    public void provide110V() {
        if (europeanSocket != null) {
            System.out.println("将220V转换为110V");
            europeanSocket.provide220V();
        } else {
            americanSocket.provide110V();
        }
    }
    
    @Override
    public void provide220V() {
        if (americanSocket != null) {
            System.out.println("将110V转换为220V");
            americanSocket.provide110V();
        } else {
            europeanSocket.provide220V();
        }
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 美国插座
        AmericanSocket americanSocket = new AmericanSocketImpl();
        
        // 欧洲插座
        EuropeanSocket europeanSocket = new EuropeanSocketImpl();
        
        // 将美国插座适配为欧洲插座
        EuropeanSocket adapter1 = new SocketAdapter(americanSocket);
        adapter1.provide220V();
        
        // 将欧洲插座适配为美国插座
        AmericanSocket adapter2 = new SocketAdapter(europeanSocket);
        adapter2.provide110V();
    }
}

优缺点

优点

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码
  2. 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性
  3. 灵活性和扩展性都非常好,通过配置文件可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合"开闭原则"

缺点

  1. 过多使用适配器会使系统变得非常凌乱,不易整体进行把握
  2. 对于类适配器来说,由于Java是单继承语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用也有一定的局限性

相关模式

  • 桥接模式:桥接模式与适配器模式类似,但桥接模式的目的是分离接口和实现,而适配器模式的目的是使接口不兼容的类能够协同工作
  • 装饰器模式:装饰器模式在不改变对象结构的情况下,动态地给对象增加一些职责,而适配器模式则是改变接口
  • 外观模式:外观模式为子系统中的所有接口提供一个统一的接口,而适配器模式则是为一个类提供另一个接口

实际应用

  • Java中的Arrays.asList()方法
  • Java中的InputStreamReader和OutputStreamWriter
  • Spring框架中的HandlerAdapter
  • Java中的java.util.Collections.enumeration()方法
  • JDBC驱动程序中的适配器模式

类适配器与对象适配器的选择

  • 类适配器:使用继承,适配器直接继承被适配者类,可以重写被适配者的方法,但只能适配一个被适配者类,且目标类必须是接口
  • 对象适配器:使用组合,适配器持有被适配者类的实例,可以适配多个被适配者类,更灵活,是更常用的方式

在Java中,由于不支持多继承,对象适配器比类适配器更常用。

相关推荐
茶本无香11 小时前
设计模式之八: 适配器模式解释及应用
java·设计模式·适配器模式
apolloyhl2 天前
Adapter 适配器模式
适配器模式
冷崖2 天前
适配器模式-结构型
适配器模式
进击的小头2 天前
结构型模式:适配器模式(C语言实现与底层实战)
c语言·适配器模式
数据与后端架构提升之路8 天前
TeleTron 源码揭秘:如何用适配器模式“无缝魔改” Megatron-Core?
人工智能·python·适配器模式
会员果汁17 天前
13.设计模式-适配器模式
设计模式·适配器模式
a35354138220 天前
设计模式-适配器模式
设计模式·适配器模式
阿闽ooo1 个月前
深入浅出适配器模式:从跨国插头适配看接口兼容的艺术
c++·设计模式·适配器模式
JavaBoy_XJ1 个月前
结构型-适配器模式
适配器模式