适配器模式
今天,我们一起来学习一下适配器模式,这个模式在日常开发中也会经常使用。在生活中也有很多适配器的例子,比如:我们和外国人讲话的时候,需要一个翻译来将我的中文翻译成英文,这也算是一种适配;最经典的例子莫过于:手机、电脑的电源适配器,我们国家的家用电电压是 220v 是不能直接接到手机上给手机充电的,手机会当场爆炸,所以我们就必须要使用手机的电源适配器将 220v 的电压适配成手机可用的电压 5v。那么,接下来我们学习一下在代码中如何体现适配器模式。
1 适配器模式定义
适配器模式:将一个类的接口转换成客户(调用者)希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
我们以下面这个图来解释一下定义的含义:
在上图中,一开始,我们有两个类 A 和 C ,想让它们互相一起完成一些功能,但是这两个类互相公开的接口要么是参数类型不同,就是返回值不一样;例如类 A 中的方法要接收的参数都是 json 类型的数据 ,而类 C 的方法返回的都是字符串数据,那么显然 A 是不能直接调用 C 的接口的。假如现在 C 类的代码我们不能修改,又想让这两个类一起完成某项功能的话,我们就可以使用适配器 B 来解决 A 和 C 之间的隔阂,B 需要将 C 的方法的返回值由字符串数据转化成 json 类型的数据以满足 A 的要求。此时 A 要想调用 C 的方法,就可以直接调用适配器中的相应方法即可,对应 A 来说,它对 C 的存在是无感的,它就只关心 B 的接口了。
适配器模式有两种类型:类适配器模式、对象适配器模式。
2 类适配器模式
类适配器模式通过继承被适配者,并将其提供的方法等转换成客户(调用者)可以直接使用的过程。
2.1 uml 图
在类适配器模式中有如下三个角色:
Resource:被适配的类。提供被适配的方法 methodA() 。
Target:适配的目标接口类 。
ResourceAdapter:就是我们说的适配器类,需要通过继承来获取 Resource 的 methodA() 方法,再实现 Target 接口,并再在 methodB() 方法中,将 methodA() 方法进行转化。
client 类:调用 Target 接口的实现类来完成一些功能。假如之前 Client 一直使用的是 Target 的一个ConcreteTarget 来完成某一些功能,但是现在业务调整需要使用 Resource 类的方法,但是 Client 与 Resource 中的方法不是很匹配不能直接使用,所以就让 ResourceAdapter 来完成适配工作。
2.2 示例
下面我们以一个小案例来实现一下该过程。
需求: 由于业务变更,使用多年的新浪天气接口不再使用,需要更改为使用墨迹天气的接口。由此带来了一个很大的问题就是,这两个接口的使用差别很大,比如:新浪接口获取天气的方法的返回值是字符串类型,我们的应用也是在此基础上建立起来的,但是现在的墨迹天气的接口返回的是 json 数据。所以需要在尽量少改动代码的情况下使我们的应用程序使用墨迹天气接口。
MoJiWeather.java:Resource:被适配的类。
该类是 墨迹天气 提供的类,由于我们没有源代码,所以是不能够进行更改的。
/**
* 墨迹天气 jar 包提供的获取天气情况的方法
*/
public class MoJiWeather {
public JSONObject getCurrentWeather(String city){
JSONObject weather = new JSONObject();
weather.put("city",city);
weather.put("temperature",20);//温度
weather.put("type","sun");//天气:下雨、太阳等
return weather;
}
}
IWeather.java:Target:适配的目标接口。
在使用墨迹天气接口之前,我们使用的 新浪天气的接口 就实现该接口。
public interface IWeather {
public String getWeather(String city);
}
MoJiWeatherAdapter.java:ResourceAdapter:在 getWeather() 方法中对 MoJiWeather 类的 getCurrentWeather() 方法进行转化。
public class MoJiWeatherAdapter extends MoJiWeather implements IWeather{
public String getWeather(String city){
JSONObject weather = getCurrentWeather(city);
//转化
String resWeather = weather.toJSONString();
return resWeather;
}
}
WeatherApplication.java:Client 类,调用 Target 实现类的地方。
对于这个类来说,它不关心它使用的是 IWeather 的哪一个实现类。
public class WeatherApplication {
private IWeather weather;
public WeatherApplication(IWeather weather){
this.weather = weather;
}
public String get(String city){
return this.weather.getWeather(city);
}
}
Test.java:测试类。
public class Test {
public static void main(String[] args) {
WeatherApplication application = new WeatherApplication(new MoJiWeatherAdapter());
System.out.println(application.get("chengdu"));
}
}
运行结果:
经过 MoJiWeatherAdapter 类的适配就使得 MoJiWeather 类 和 WeatherApplication 类能够一起工作了。并且对于 WeatherApplication 来说,它并不知道 MoJiWeather 的存在。
2.3 缺点
类适配器模式的缺点:
- 由于 ResourceAdapter 继承自 Resource ,所以在 ResourceAdapter 中暴露了 Resource 中的方法,如果方法很多的话,在使用 ResourceAdapter 时增加了使用成本。
- 不符合合成用原则。
3 对象适配器模式
对象适配器模式与类适配器模式差不多,只有一点不同,对象适配器模式中 ResourceAdapter 不继承 Resource 而是 聚合 Resource。这样也能完成适配功能,并且还符合合成复用原则。
合成复用原则:尽量使用 关联关系(组合、聚合) 来替代继承关系。
3.1 uml 图
该 uml 图与 类适配器的 uml 图基本一致。
3.2 示例:
经过前面的分析,我们只需要修改 MoJiWeatherAdapter 类即可。
MoJiWeatherAdapter.java:适配器类。
public class MoJiWeatherAdapter implements IWeather {
//聚合墨迹天气接口类
private MoJiWeather moJiWeather;
public MoJiWeatherAdapter(){
this.moJiWeather = new MoJiWeather();
}
public String getWeather(String city){
JSONObject weather = this.moJiWeather.getCurrentWeather(city);
//转化
String resWeather = weather.toJSONString();
return resWeather;
}
}
对象适配器的效果与类适配器的效果一样,只是在实现方式上有略微差别罢了。
4 总结
适配器模式的应用场景:
- java 程序与数据库系统之间的适配器:JDBC
- SpringMvc 中的 HandlerAdapter 。