适配器模式
一. 说明
适配器模式的主要作用就是把不兼容的接口通过适配修改转化成兼容的接口,方便我们统一调用。生活中像我们常见的电源适配器,统一的数据线分接头的例子都是使用了适配器去解决各种不兼容的场景。
当两个类所作的事情是类似或相同的,但具有不同的接口时,我们就可以使用适配器模式
,适配器模式使得在很多灵活变动的场景下我们可以分离变化的部分和不变的部分,变化的部分通过适配器转化成统一形式进行不变的逻辑处理。
二.应用场景
- 两个不同国家的人相互交流,就需要翻译作为适配器
- 开发中我们实现相同功能但需要对接不同平台的API时,需要适配器将不同的API转化成统一的数据形式进行处理
- Spring框架中有大量使用适配器的地方,一般以Adapter结尾的类都是使用的适配器模式
三.代码示例
以查询第三方天气接口为例,假设我们项目中使用的是百度天气进行天气查询的,先建一个查询天气的接口
public interface IWeather {
/**
* 根据地区码查询地区天气
* @param areaCode
* @return
*/
Weather queryWeather(String areaCode);
}
//天气实体对象
@Data
public class Weather implements Serializable {
private String area;
private String areaCode;
private String weather;
private String temperature;
private String windSpeed;
}
再实现这个接口,实现方式最初是使用百度天气
public class WeatherApi implements IWeather{
private static final String BAIDU_WEATHER_URL = "https://api.map.baidu.com/weather/v1/?district_id=%s&data_type=all&ak=%s";
@Override
public Weather queryWeather(String areaCode) {
String key = "test";
Response data = HttpRequest.sendRequest(String.format(BAIDU_WEATHER_URL, areaCode, key));
Weather weather = Weather.convert(data);
return weather;
}
}
经过一段时间,我们发现单独使用某一个第三方没有稳定性,我们现在又要集成腾讯和高德的天气服务,我们希望原有代码不做变更不产生影响而扩展其他两个,这个时候使用适配器可以达到效果。
我们先建立一个天气适配器接口,然后实现适配器
public interface WeatherAdapter {
//请求腾讯天气
Weather queryTencentWeather(String areaCode);
//请求阿里天气
Weather queryAliWeather(String areaCode);
}
public class WeatherAdapterImpl implements WeatherAdapter{
private static final String TENCENT_WEATHER_URL = "http://t.weather.sojson.com/api/weather/city/%s";
private static final String GAODE_AMAP_URL = "https://restapi.amap.com/v3/weather/weatherInfo?city=%s&key=%s";
@Override
public Weather queryTencentWeather(String areaCode) {
String key = "test";
Response data = HttpRequest.sendRequest(String.format(TENCENT_WEATHER_URL, areaCode, key));
Weather weather = Weather.convert(data);
return weather;
}
@Override
public Weather queryAmapWeather(String areaCode) {
String key = "test";
Response data = HttpRequest.sendRequest(String.format(GAODE_AMAP_URL, areaCode, key));
Weather weather = Weather.convert(data);
return weather;
}
}
扩展IWeather和实现WeatherApi的方法,根据传入渠道选择第三方进行天气查询
public interface IWeather {
Weather queryWeather(String areaCode);
Weather queryWeather(String channel, String areaCode);
}
//实现传入渠道,根据渠道分别查询对应的天气api
public class WeatherApi implements IWeather {
private static final String BAIDU_WEATHER_URL = "https://api.map.baidu.com/weather/v1/?district_id=%s&data_type=all&ak=%s";
private WeatherAdapter weatherAdapter = new WeatherAdapterImpl();
@Override
public Weather queryWeather(String areaCode) {
String key = "test";
Response data = HttpRequest.sendRequest(String.format(BAIDU_WEATHER_URL, areaCode, key));
Weather weather = Weather.convert(data);
return weather;
}
@Override
public Weather queryWeather(String channel, String areaCode) {
Weather weather = null;
switch (channel) {
case "tencent":
weather = weatherAdapter.queryTencentWeather(areaCode);
break;
case "amap":
weather = weatherAdapter.queryAmapWeather(areaCode);
break;
case "baidu":
weather = queryWeather(areaCode);
break;
}
return weather;
}
}
此时我们使用适配器既扩展了两个渠道的api,也没有影响到原有的代码,非常完美。
此处获取用策略模式更能体现设计模式的优越性,用适配器模式只是为了说明这种情况下用适配器模式也能达到目的。
在Spring中,我们常用的AOP切面编程就是适配器的典型用例,在Spring AOP中BeforeAdvice,AfterAdvice,ThrowAdvice这三种通知类型就是使用的适配器模式实现的,它扩充了aop的通知类型使得用户可以任意将某种通知加入到自己的应用中。三种扩展的通知类型对应的接口是AdvisorAdapter,它有几个实现类:MethodBeforAdviceAdapter、AfterReturnningAdviceAdapter、ThrowsAdviceAdapter,都是包装返回不同的MethodInterceptor进行aop拦截。
四. 总结
适配器模式的使用场景非常多,我们在一般业务开发中可能不自觉的已经使用了这种模式但不被我们发现,只要思想是适配器模式的思想,写法是千变万化的。在项目中也不可过多的使用适配器,过多地使用适配器,会让系统非常零乱,不易整体进行把握。