23种设计模式23种设计模式
首页
介绍
  • 单例模式
  • 工厂方法模式
  • 抽象工厂模式
  • 建造者模式
  • 原型模式
  • 适配器模式
  • 桥接模式
  • 组合模式
  • 装饰器模式
  • 外观模式
  • 享元模式
  • 代理模式
  • 责任链模式
  • 命令模式
  • 解释器模式
  • 迭代器模式
  • 中介者模式
  • 备忘录模式
  • 观察者模式
  • 状态模式
  • 策略模式
  • 模板方法模式
  • 访问者模式
🚀 编程指南
首页
介绍
  • 单例模式
  • 工厂方法模式
  • 抽象工厂模式
  • 建造者模式
  • 原型模式
  • 适配器模式
  • 桥接模式
  • 组合模式
  • 装饰器模式
  • 外观模式
  • 享元模式
  • 代理模式
  • 责任链模式
  • 命令模式
  • 解释器模式
  • 迭代器模式
  • 中介者模式
  • 备忘录模式
  • 观察者模式
  • 状态模式
  • 策略模式
  • 模板方法模式
  • 访问者模式
🚀 编程指南
  • 结构型模式

    • 适配器模式 (Adapter)
    • 桥接模式 (Bridge)
    • 组合模式 (Composite)
    • 装饰器模式 (Decorator)
    • 外观模式 (Facade)
    • 享元模式 (Flyweight)
    • 代理模式 (Proxy)

适配器模式 (Adapter)

📖 通俗理解

出国旅行时,你带的是中国的电器插头(两孔/三孔),但国外的插座是圆孔的。怎么办?

买一个转换插头!它一边连接你的插头,一边连接国外的插座,让它们能一起工作。

适配器模式就是:让两个不兼容的接口可以一起工作,起到一个"转换器"的作用。

🎯 解决什么问题?

场景:你有一个旧的系统,现在要接入一个新的第三方库,但接口不兼容。

// 旧系统的接口
interface OldSystem {
    void oldMethod();
}

// 新的第三方库
class NewLibrary {
    void newMethod() { ... }
}

// 问题:旧系统调用 oldMethod(),但新库只有 newMethod()
// 怎么让它们一起工作?

解决方案:写一个适配器,把新库的接口"翻译"成旧系统能理解的接口。

🌰 生活中的例子

  • 电源适配器:把220V转换成笔记本需要的电压
  • 翻译官:让说不同语言的人交流
  • 读卡器:让电脑能读取SD卡
  • Type-C转接头:让老设备能用新接口

💻 Java 代码实现

场景:播放器适配

我们有一个只能播放 MP3 的老播放器,现在要让它也能播放 MP4 和 VLC 格式。

方式一:类适配器(继承方式)

/**
 * 目标接口:客户端期望的接口
 */
public interface MediaPlayer {
    void play(String audioType, String fileName);
}

/**
 * 被适配者:高级播放器(只会播放高级格式)
 */
public class AdvancedMediaPlayer {
    
    public void playVlc(String fileName) {
        System.out.println("播放 VLC 文件: " + fileName);
    }
    
    public void playMp4(String fileName) {
        System.out.println("播放 MP4 文件: " + fileName);
    }
}

/**
 * 适配器:继承被适配者,实现目标接口
 */
public class MediaAdapter extends AdvancedMediaPlayer implements MediaPlayer {
    
    @Override
    public void play(String audioType, String fileName) {
        if ("vlc".equalsIgnoreCase(audioType)) {
            playVlc(fileName);
        } else if ("mp4".equalsIgnoreCase(audioType)) {
            playMp4(fileName);
        }
    }
}

/**
 * 音频播放器:使用适配器
 */
public class AudioPlayer implements MediaPlayer {
    
    private MediaAdapter adapter;
    
    @Override
    public void play(String audioType, String fileName) {
        // 内置支持 MP3
        if ("mp3".equalsIgnoreCase(audioType)) {
            System.out.println("播放 MP3 文件: " + fileName);
        }
        // 使用适配器播放其他格式
        else if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)) {
            adapter = new MediaAdapter();
            adapter.play(audioType, fileName);
        }
        else {
            System.out.println("不支持的格式: " + audioType);
        }
    }
}

测试代码:

public class Client {
    public static void main(String[] args) {
        AudioPlayer player = new AudioPlayer();
        
        player.play("mp3", "beyond.mp3");
        player.play("mp4", "alone.mp4");
        player.play("vlc", "movie.vlc");
        player.play("avi", "video.avi");
    }
}

输出:

播放 MP3 文件: beyond.mp3
播放 MP4 文件: alone.mp4
播放 VLC 文件: movie.vlc
不支持的格式: avi

方式二:对象适配器(组合方式)⭐ 推荐

/**
 * 目标接口
 */
public interface Target {
    void request();
}

/**
 * 被适配者:已有的类,接口不兼容
 */
public class Adaptee {
    public void specificRequest() {
        System.out.println("Adaptee: 执行特殊请求");
    }
}

/**
 * 对象适配器:通过组合持有被适配者
 */
public class ObjectAdapter implements Target {
    
    private Adaptee adaptee;
    
    public ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    
    @Override
    public void request() {
        // 把客户端的请求转换为被适配者能理解的方法
        System.out.println("Adapter: 转换请求...");
        adaptee.specificRequest();
    }
}

使用方式:

public class Client {
    public static void main(String[] args) {
        // 创建被适配者
        Adaptee adaptee = new Adaptee();
        
        // 用适配器包装
        Target target = new ObjectAdapter(adaptee);
        
        // 客户端只和 Target 接口打交道
        target.request();
    }
}

🔥 实战案例:接入第三方登录

假设你的系统有统一的登录接口,现在要接入微信、支付宝等第三方登录。

/**
 * 系统统一的登录接口
 */
public interface LoginService {
    /**
     * 统一登录方法
     * @param token 登录凭证
     * @return 用户信息
     */
    UserInfo login(String token);
}

/**
 * 用户信息
 */
public class UserInfo {
    private String id;
    private String name;
    private String avatar;
    // getter setter constructor...
}

// ========== 第三方 SDK(接口各不相同)==========

/**
 * 微信 SDK
 */
public class WechatApi {
    public WechatUser getWechatUser(String code) {
        System.out.println("调用微信API,code: " + code);
        return new WechatUser("wx_123", "微信用户", "wx_avatar.png");
    }
}

public class WechatUser {
    private String openId;
    private String nickname;
    private String headImgUrl;
    // ...
}

/**
 * 支付宝 SDK
 */
public class AlipayApi {
    public AlipayUser getUserInfo(String authCode) {
        System.out.println("调用支付宝API,authCode: " + authCode);
        return new AlipayUser("ali_456", "支付宝用户", "ali_avatar.png");
    }
}

public class AlipayUser {
    private String userId;
    private String userName;
    private String avatar;
    // ...
}

// ========== 适配器:将第三方接口适配为统一接口 ==========

/**
 * 微信登录适配器
 */
public class WechatLoginAdapter implements LoginService {
    
    private WechatApi wechatApi = new WechatApi();
    
    @Override
    public UserInfo login(String token) {
        // 调用微信 API
        WechatUser wechatUser = wechatApi.getWechatUser(token);
        
        // 转换为统一的用户信息格式
        return new UserInfo(
            "wechat_" + wechatUser.getOpenId(),
            wechatUser.getNickname(),
            wechatUser.getHeadImgUrl()
        );
    }
}

/**
 * 支付宝登录适配器
 */
public class AlipayLoginAdapter implements LoginService {
    
    private AlipayApi alipayApi = new AlipayApi();
    
    @Override
    public UserInfo login(String token) {
        // 调用支付宝 API
        AlipayUser alipayUser = alipayApi.getUserInfo(token);
        
        // 转换为统一的用户信息格式
        return new UserInfo(
            "alipay_" + alipayUser.getUserId(),
            alipayUser.getUserName(),
            alipayUser.getAvatar()
        );
    }
}

使用方式:

public class LoginController {
    
    public UserInfo login(String type, String token) {
        LoginService loginService;
        
        switch (type) {
            case "wechat":
                loginService = new WechatLoginAdapter();
                break;
            case "alipay":
                loginService = new AlipayLoginAdapter();
                break;
            default:
                throw new IllegalArgumentException("不支持的登录方式: " + type);
        }
        
        // 统一的调用方式
        return loginService.login(token);
    }
    
    public static void main(String[] args) {
        LoginController controller = new LoginController();
        
        // 微信登录
        UserInfo user1 = controller.login("wechat", "wx_code_123");
        System.out.println("登录成功: " + user1.getName());
        
        // 支付宝登录
        UserInfo user2 = controller.login("alipay", "ali_code_456");
        System.out.println("登录成功: " + user2.getName());
    }
}

📊 类图对比

类适配器(继承)

┌────────────┐     ┌────────────┐
│  Target    │     │  Adaptee   │
│ +request() │     │ +specific  │
│            │     │  Request() │
└─────▲──────┘     └─────▲──────┘
      │                  │
      │   ┌──────────────┘
      │   │
 ┌────┴───┴────┐
 │   Adapter   │  继承 Adaptee
 │ +request()  │  实现 Target
 └─────────────┘

对象适配器(组合)⭐

┌────────────┐     ┌────────────┐
│  Target    │     │  Adaptee   │
│ +request() │     │ +specific  │
│            │     │  Request() │
└─────▲──────┘     └─────────────┘
      │                  ▲
      │                  │ 组合
 ┌────┴───────┐          │
 │  Adapter   │──────────┘
 │ -adaptee   │  持有 Adaptee 引用
 │ +request() │
 └────────────┘

✅ 适用场景

  1. 使用第三方库:接口不兼容时
  2. 系统升级:新老接口不一致
  3. 统一接口:多个类似功能的类,统一为一个接口

⚠️ 类适配器 vs 对象适配器

对比项类适配器对象适配器
实现方式继承组合
灵活性低(Java 单继承限制)高
可适配范围只能适配一个类可适配多个类
推荐程度⭐⭐⭐⭐⭐⭐⭐

小结

适配器模式的核心:在不修改原有代码的情况下,让不兼容的接口可以一起工作。

简单记忆:适配器 = 转换插头 = 翻译官

👉 下一篇:桥接模式

Next
桥接模式 (Bridge)