Java设计模式之适配器模式(Adapter)

适配器模式是一种结构型设计模式,它允许不兼容的接口之间能够协同工作。就像现实生活中的电源适配器一样,它能让不同标准的电器设备正常工作。

什么是适配器模式

适配器模式就像一个"翻译官",它将一个类的接口转换成客户端期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。

生活中的例子

想象一下,你有一个只有USB-C接口的笔记本电脑,但你的鼠标是USB接口的。这时你需要一个USB转USB-C的适配器,这个适配器就是适配器模式的现实体现。

适配器模式的结构

适配器模式主要包含三个角色:

  1. 目标(Target):客户端期望的接口
  2. 适配者(Adaptee):需要被适配的接口
  3. 适配器(Adapter):将适配者接口转换为目标接口

适配器模式的实现方式

适配器模式有两种实现方式:类适配器和对象适配器。

1. 类适配器(使用继承)

// 目标接口
public interface Target {
    void request();
}
// 适配者类
public class Adaptee {
    public void specificRequest() {
        System.out.println("适配者的特殊请求");
    }
}
// 类适配器
public class ClassAdapter extends Adaptee implements Target {
    @Override
    public void request() {
        specificRequest();
    }
}

2. 对象适配器(使用组合)

// 目标接口
public interface Target {
    void request();
}
// 适配者类
public class Adaptee {
    public void specificRequest() {
        System.out.println("适配者的特殊请求");
    }
}
// 对象适配器
public class ObjectAdapter implements Target {
    private Adaptee adaptee;
  
    public ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
  
    @Override
    public void request() {
        adaptee.specificRequest();
    }
}

完整代码示例

让我们通过一个完整的例子来理解适配器模式。假设我们有一个媒体播放器,它只能播放MP3格式的文件,但我们希望它也能播放其他格式的文件。

1. 定义目标接口

// 目标接口
public interface MediaPlayer {
    void play(String audioType, String fileName);
}

2. 定义适配者接口和实现

// 适配者接口
public interface AdvancedMediaPlayer {
    void playVlc(String fileName);
    void playMp4(String fileName);
}
// 适配者实现1
public class VlcPlayer implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        System.out.println("Playing vlc file: " + fileName);
    }
  
    @Override
    public void playMp4(String fileName) {
        // 什么也不做
    }
}
// 适配者实现2
public class Mp4Player implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        // 什么也不做
    }
  
    @Override
    public void playMp4(String fileName) {
        System.out.println("Playing mp4 file: " + fileName);
    }
}

3. 创建适配器

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

4. 创建音频播放器实现

// 音频播放器实现
public class AudioPlayer implements MediaPlayer {
    private MediaAdapter mediaAdapter;
  
    @Override
    public void play(String audioType, String fileName) {
        // 内置支持MP3格式
        if (audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing mp3 file: " + fileName);
        }
        // 使用适配器支持其他格式
        else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            mediaAdapter = new MediaAdapter(audioType);
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("Invalid media. " + audioType + " format not supported");
        }
    }
}

5. 测试代码

public class AdapterPatternDemo {
    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");
    }
}

输出结果:

Playing mp3 file: beyond the horizon.mp3
Playing mp4 file: alone.mp4
Playing vlc file: far far away.vlc
Invalid media. avi format not supported

适配器模式在Spring Boot中的应用

在Spring Boot 2.7.18中,适配器模式有很多应用场景,下面介绍几个常见的例子。

1. Spring MVC中的HandlerAdapter

Spring MVC使用HandlerAdapter来处理不同类型的控制器。每个控制器类型都有对应的HandlerAdapter,这样DispatcherServlet就可以通过统一的接口调用不同类型的控制器。

@Controller
public class MyController {
    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }
}
// Spring内部使用HandlerAdapter来适配不同类型的控制器
// 我们不需要自己实现,这是Spring内部的实现

2. Spring Boot中的消息转换器(HttpMessageConverter)

HttpMessageConverter将HTTP请求和响应转换为Java对象,这也是适配器模式的应用。

@RestController
@RequestMapping("/api")
public class UserController {
  
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // 返回User对象,HttpMessageConverter会将其转换为JSON
        return new User(id, "John Doe", "john@example.com");
    }
  
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        // HttpMessageConverter将请求体转换为User对象
        // 保存用户并返回
        return user;
    }
}
// User类
public class User {
    private Long id;
    private String name;
    private String email;
  
    // 构造方法、getter和setter
}

3. 自定义适配器示例

假设我们有一个旧的用户服务接口,现在需要适配到新的接口。

// 旧的用户服务接口
public interface LegacyUserService {
    String getUserData(String userId);
}
// 旧的用户服务实现
@Service
public class LegacyUserServiceImpl implements LegacyUserService {
    @Override
    public String getUserData(String userId) {
        // 返回JSON字符串格式的用户数据
        return "{\"id\":\"" + userId + "\", \"name\":\"Legacy User\"}";
    }
}
// 新的用户服务接口
public interface NewUserService {
    User getUser(String userId);
}
// 用户类
public class User {
    private String id;
    private String name;
  
    // 构造方法、getter和setter
}
// 适配器实现
@Service
public class UserServiceAdapter implements NewUserService {
  
    @Autowired
    private LegacyUserService legacyUserService;
  
    @Override
    public User getUser(String userId) {
        String userData = legacyUserService.getUserData(userId);
        // 解析JSON字符串并转换为User对象
        // 这里简化处理,实际项目中可以使用Jackson等库
        User user = new User();
        user.setId(userId);
        user.setName("Legacy User");
        return user;
    }
}
// 控制器
@RestController
@RequestMapping("/users")
public class UserController {
  
    @Autowired
    private NewUserService userService;
  
    @GetMapping("/{id}")
    public User getUser(@PathVariable String id) {
        return userService.getUser(id);
    }
}

适配器模式的优缺点

优点

  1. 单一职责原则:可以将接口转换代码从业务逻辑中分离出来
  2. 开闭原则:可以在不修改现有代码的情况下引入新的适配器
  3. 提高复用性:使得原本由于接口不兼容而不能一起工作的类可以协同工作
  4. 灵活性:可以在系统运行时切换不同的实现

缺点

  1. 增加复杂性:增加了系统中类的数量,使系统变得更加复杂
  2. 过度使用:如果不需要解决接口兼容问题,不应该使用适配器模式

总结

适配器模式是一种非常实用的设计模式,它可以帮助我们解决接口不兼容的问题。在Spring Boot中,适配器模式被广泛应用,特别是在处理不同类型的控制器、消息转换等方面。
通过适配器模式,我们可以让不兼容的接口协同工作,提高代码的复用性和灵活性。在实际开发中,当我们需要集成第三方库或旧系统时,适配器模式是一个很好的选择。
希望这篇文章能帮助你理解适配器模式,并在实际开发中灵活运用它!