什么是适配器模式?
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许接口不兼容的对象能够相互合作。简单来说,适配器模式就像我们日常生活中的电源适配器,它能够连接两个原本不兼容的接口,使它们能够一起工作。
适配器模式的核心思想是:将一个类的接口转换成客户端所期望的另一个接口,使得原本因接口不兼容而无法一起工作的类能够协同工作。
适配器模式的结构
适配器模式主要包含以下角色:
- 目标接口(Target):目标接口,客户端所期望的接口
- 适配者(Adaptee):需要被适配的类或接口,但是与Target不兼容
- 适配器(Adapter):连接目标接口和适配者的中间件
- 用户(Client):需要适配器的对象
适配器模式有两种实现方式:
- 类适配器:使用继承的方式
- 对象适配器:使用组合的方式(更常用)
一个简单的适配器模式示例
假设我们有一个老系统中的 LegacyUser
类,但我们的新系统需要使用 User
接口。我们可以创建一个适配器来解决这个问题:
java
// 目标接口 (Target)
public interface User {
String getName();
String getEmail();
String getPhone();
}
// 适配者 (Adaptee) - 老系统中的类
public class LegacyUser {
private String username;
private String contact;
private String telephone;
// 构造函数、getter和setter省略
public String getUsername() {
return username;
}
public String getContact() {
return contact;
}
public String getTelephone() {
return telephone;
}
}
// 适配器 (Adapter)
public class LegacyUserAdapter implements User {
private LegacyUser legacyUser;
public LegacyUserAdapter(LegacyUser legacyUser) {
this.legacyUser = legacyUser;
}
@Override
public String getName() {
return legacyUser.getUsername();
}
@Override
public String getEmail() {
return legacyUser.getContact();
}
@Override
public String getPhone() {
return legacyUser.getTelephone();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建一个老系统的用户
LegacyUser oldUser = new LegacyUser();
oldUser.setUsername("张三");
oldUser.setContact("zhangsan@example.com");
oldUser.setTelephone("13800138000");
// 使用适配器将老系统用户适配到新系统
User adaptedUser = new LegacyUserAdapter(oldUser);
// 现在可以使用新系统的接口了
System.out.println("姓名: " + adaptedUser.getName());
System.out.println("邮箱: " + adaptedUser.getEmail());
System.out.println("电话: " + adaptedUser.getPhone());
}
}
Spring Boot 中的适配器模式
Spring Boot 框架中大量使用了适配器模式,下面我们来看几个典型的例子:
1. Spring MVC 中的 HandlerAdapter
在 Spring MVC 中,DispatcherServlet
是前端控制器,负责将请求分发给不同的处理器(Handler)。但是这些处理器的类型可能各不相同(如 Controller、HttpRequestHandler 等),它们的接口也不一致。
为了统一处理这些不同类型的处理器,Spring MVC 使用了适配器模式,引入了 HandlerAdapter
接口:
java
// 目标接口
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
Spring MVC 为不同类型的处理器提供了不同的适配器实现:
java
// 适配器实现示例 - 为 @RequestMapping 注解的控制器提供适配
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof HandlerMethod;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 处理请求的逻辑
return handleInternal(request, response, (HandlerMethod) handler);
}
// 其他方法实现...
}
DispatcherServlet
的处理流程大致如下:
java
// DispatcherServlet 中的处理逻辑(简化版)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 找到处理请求的 handler
Object handler = getHandler(request);
// 找到适合该 handler 的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(handler);
// 使用适配器处理请求
ModelAndView mv = ha.handle(request, response, handler);
// 处理视图...
}
// 查找合适的适配器
private HandlerAdapter getHandlerAdapter(Object handler) {
for (HandlerAdapter ha : this.handlerAdapters) {
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler + "]");
}
通过这种方式,Spring MVC 可以支持多种类型的处理器,而 DispatcherServlet
不需要知道具体处理器的实现细节。
2. Spring Boot 中的 WebMvcConfigurer 适配器
在 Spring Boot 2.0 之前,我们通常通过继承 WebMvcConfigurerAdapter
来自定义 MVC 配置:
java
// Spring Boot 1.x 中的用法
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
WebMvcConfigurerAdapter
就是一个适配器类,它实现了 WebMvcConfigurer
接口的所有方法(提供空实现),这样我们只需要重写需要的方法即可。
在 Java 8 之后,接口可以有默认方法,所以在 Spring Boot 2.0 中,WebMvcConfigurerAdapter
被废弃了,我们可以直接实现 WebMvcConfigurer
接口:
java
// Spring Boot 2.x 中的用法
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
3. Spring Boot 中的 MessageConverter
Spring Boot 中的 HttpMessageConverter
也是适配器模式的一个很好的例子。它负责将 HTTP 请求体转换为 Java 对象,或将 Java 对象转换为 HTTP 响应体。
java
// 目标接口
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
Spring Boot 提供了多种 HttpMessageConverter
的实现,如 MappingJackson2HttpMessageConverter
(处理 JSON)、MarshallingHttpMessageConverter
(处理 XML)等。
实现一个自定义的 Spring Boot 适配器
让我们实现一个自定义的适配器,将第三方 API 的数据格式适配到我们的系统中:
假设我们有一个第三方天气 API,但它的数据格式与我们系统需要的格式不同:
java
// 第三方天气 API 提供的数据格式
public class ThirdPartyWeatherData {
private String cityCode;
private double temp;
private int humidity;
private String weatherCondition;
// 构造函数、getter和setter省略
}
// 我们系统中使用的天气数据格式
public interface WeatherInfo {
String getCityName();
double getTemperature();
int getHumidity();
String getCondition();
boolean isRainy();
}
// 适配器实现
@Service
public class WeatherAdapter implements WeatherInfo {
private final ThirdPartyWeatherData thirdPartyData;
private final CityService cityService; // 用于将城市代码转换为城市名称
@Autowired
public WeatherAdapter(ThirdPartyWeatherService thirdPartyService, CityService cityService) {
this.thirdPartyData = thirdPartyService.getWeatherData();
this.cityService = cityService;
}
@Override
public String getCityName() {
// 将城市代码转换为城市名称
return cityService.getCityNameByCode(thirdPartyData.getCityCode());
}
@Override
public double getTemperature() {
// 假设第三方API返回的是华氏度,我们需要转换为摄氏度
return (thirdPartyData.getTemp() - 32) * 5 / 9;
}
@Override
public int getHumidity() {
return thirdPartyData.getHumidity();
}
@Override
public String getCondition() {
return thirdPartyData.getWeatherCondition();
}
@Override
public boolean isRainy() {
// 根据天气状况判断是否下雨
String condition = thirdPartyData.getWeatherCondition().toLowerCase();
return condition.contains("rain") || condition.contains("drizzle");
}
}
// 控制器中使用适配器
@RestController
@RequestMapping("/weather")
public class WeatherController {
private final WeatherInfo weatherInfo;
@Autowired
public WeatherController(WeatherAdapter weatherAdapter) {
this.weatherInfo = weatherAdapter;
}
@GetMapping("/current")
public Map<String, Object> getCurrentWeather() {
Map<String, Object> result = new HashMap<>();
result.put("city", weatherInfo.getCityName());
result.put("temperature", weatherInfo.getTemperature());
result.put("humidity", weatherInfo.getHumidity());
result.put("condition", weatherInfo.getCondition());
result.put("isRainy", weatherInfo.isRainy());
return result;
}
}
适配器模式的优缺点
优点:
- 增加了类的透明性:通过适配器,客户端可以调用同一接口,无需关心底层实现
- 提高了类的复用性:适配器可以让原本不兼容的类一起工作
- 灵活性和扩展性好:可以在不修改原有代码的情况下,引入并使用新的组件
缺点:
- 增加了系统的复杂性:引入了新的类和接口
- 可能会导致代码可读性下降:如果过度使用适配器,可能会让系统变得难以理解
适配器模式的应用场景
- 需要使用一个已存在的类,但其接口不符合需求时
- 需要统一多个类的接口时
- 需要复用一些现有的类,但不能修改其源代码时
- 需要在新旧系统之间建立联系时
总结
适配器模式是一种非常实用的设计模式,它帮助我们解决接口不兼容的问题。在 Spring Boot 等框架中,适配器模式被广泛应用,使得框架能够灵活地支持各种不同的组件和实现。