适配器模式(Adapter Pattern)又叫做变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作,属于结构型设计模式。
也就是说,当前系统存在两种接口A和B,客户只支持访问A接口,但是当前系统没有A接口对象,但是有B接口对象,但客户无法识别B接口,因此需要通过一个适配器C,将B接口内容转换成A接口,从而使得客户能够从A接口获取得到B接口内容。
在软件开发中,基本上任何问题都可以通过增加一个中间层进行解决。适配器模式其实就是一个中间层。综上,适配器模式其实起着转化/委托的作用,将一种接口转化为另一种符合需求的接口。
提供一个转换器(适配器),将当前系统存在的一个对象转换为客户端能够访问接口对象。适配器使用于以下几种业务场景:
1、已经存在的类,他的方法和需求不匹配(方法结果相同或相似)的情况。
2、适配器模式不是软件设计阶段考虑的设计模式,适用于软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。有点亡羊补牢的感觉。
生活中也非常的应用场景,例如电源转换头、手机充电转换头、显示器转接头。
适配器模式一般包含三种角色:
目标角色(Target):也就是我们期望的接口;
源角色(Adaptee):存在于系统中,内容满足客户需求(需转换),但接口不匹配的接口实例;
适配器(Adapter):将源角色(Adaptee)转化为目标角色(Target)的类实例;
适配器模式个角色之间的关系如下:
假设当前系统中,客户端需要访问的是Target接口,但Target接口没有一个实例符合需求,而Adaptee实例符合需求;但是客户端无法直接使用Adpatee(接口不兼容);因此,我们需要一个适配器(Adapter)来进行中转,让Adaptee能转换为Target接口形式;
适配器模式有3种形式:类适配器、对象适配器、接口适配器。
类适配器的原理就是通过继承来实现适配器功能。具体做法L让Adapter实现Target接口并且继承Adaptee,这样Adapter就具备Target和Adaptee,这样Adapter就具备Target和Adaptee的特性,就可以将两者进行转化。
下面我们以一个示例进行讲解,来看下该示例分别用类适配器,对象适配器和接口适配器是怎样进行代码实现。在中国民用带你都是220V交流电,但我们手机使用的锂电池使用的5V直流电。因此,我们给手机充电时就需要使用电源适配器来进行转换。下面我们有代码来还愿这个生活场景。创建Adaptee角色,需要被转换的对象AC220类,表示220V交流电:
public class AC220{
public int outputAC220V() {
int output = 220;
System.out.println("输出电压" + output + "V");
return output;
}
}
创建Target角色DC5接口,表示5V直流电的标准:
public interface DC5 {
int output5V();
}
```java
创建Adapter角色电源适配器PowerAdapter类:
public class PowerAdapter extends AC220 implements DC5 {
public int output5V() {
int adapterInput = super.outputAC220V();
int adapterOutput = adapterInput / 44;
System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
return adapterOutput;
}
}
客户端测试代码:
public class Test {
public static void main(String[] args) {
DC5 adapter = new PowerAdapter();
adapter.output5V();
}
}
上面的案例中,通过增加PowerAdapter电源适配器,实现了二者的兼容。
对象适配器的原理就是通过组合来实现适配器功能。具体做法:让Adapter实现Target接口,然后内部持有Adaptee实例,然后再Target接口规定的方法内转换Adaptee。
代码只需要更改适配器(Adapter)实例,其它与类适配器一致:
public class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
public int output5V() {
int adapterInput = ac220.outputAC220V();
int adapterOutput = adapterInput / 44;
System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
return adapterOutput;
}
}
接口适配器的关注点与类适配器和对象适配器的关注点不太一样,类适配器和对象适配器着重将系统存在的一个角色(Adaptee)转化成目标接口(Target)所需内容,而接口适配器的使用场景是解决接口方法过多,如果直接实现接口,那么类会多处许多空实现的方法。类显得臃肿。此时,使用接口适配器就能让我们只实现我们需要的接口方法,目标更清晰。
接口适配器的主要原理就是利用抽象类实现接口,并且空实现接口众多方法。下面我们来接口适配器的源码实现,首先Target角色的DC类:
public interface DC {
int output5V();
int output12V();
int output24V();
int output36V();
}
创建Adaptee角色AC220类:
public class AC220 {
public int outputAC220V(){
int output = 220;
System.out.println("输出电压" + 220 + "V");
return output;
}
}
创建Adapter角色PowerAdapter类:
public class PowerAdapter implements DC {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
public int output5V() {
int adapterInput = ac220.outputAC220V();
int adapterOutput = adapterInput / 44;
System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
return adapterOutput;
}
public int output12V() {
return 0;
}
public int output24V() {
return 0;
}
public int output36V() {
return 0;
}
}
客户端代码:
public class Test {
public static void main(String[] args) {
DC adapter = new PowerAdapter(new AC220());
adapter.output5V();
}
}
下面我们来一个实际的业务场景,利用适配器模式来解决实际问题。年纪稍微大一点的小伙伴一定经历过这样一个过程。我们很早以前开发的老系统应该都有登录接口,但是随着业务的发展和社会的进步,单纯依赖用户名和密码登录显然不能满足用户的需求了。现在,我们大部分系统都已经支持多种登录方式,如QQ登录、微信登录、手机登录、微博登录等等,同时保留用户名的登录方式。虽然登录形式丰富了,但是登录后的处理逻辑可以不必改,同样是将登录状态保存到session,遵循开闭原则。首先创建统一的返回结果ResultMsg类:
public class ResultMsg {
private int code;
private String msg;
private Object data;
public ResultMsg(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
假设老系统的登录逻辑PassportService:
public class PassportService {
/**
* 注册方法
* @param username
* @param password
* @return
*/
public ResultMsg regist(String username,String password){
return new ResultMsg(200,"注册成功",new Member());
}
/**
* 登录的方法
* @param username
* @param password
* @return
*/
public ResultMsg login(String username,String password){
return null;
}
}
为了遵循开闭原则,老系统的代码我们不会去修改。那么下面开启代码重构之路,先创建Member类:
public class Member {
private String username;
private String password;
private String mid;
private String info;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMid() {
return mid;
}
public void setMid(String mid) {
this.mid = mid;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
运行非常稳定的代码我们不去改动,首先创建Target角色IPassportForThird接口:
public interface IPassportForThird {
ResultMsg loginForQQ(String openId);
ResultMsg loginForWechat(String openId);
ResultMsg loginForToken(String token);
ResultMsg loginForTelphone(String phone,String code);
}
然后创建适配器Adapter角色实现兼容,创建一个新的类PassportForThirdAdapter继承原来的逻辑:
public class PassportForThirdAdapter extends PassportService implements IPassportForThird {
public ResultMsg loginForQQ(String openId) {
return loginForRegist(openId,null);
}
public ResultMsg loginForWechat(String openId) {
return loginForRegist(openId,null);
}
public ResultMsg loginForToken(String token) {
return loginForRegist(token,null);
}
public ResultMsg loginForTelphone(String phone, String code) {
return loginForRegist(phone,null);
}
private ResultMsg loginForRegist(String username,String password){
if(null == password){
password = "THIRD_EMPTY";
}
super.regist(username,password);
return super.login(username,password);
}
}
客户端测试代码:
public class Test {
public static void main(String[] args) {
PassportForThirdAdapter adapter = new PassportForThirdAdapter();
adapter.login("tom","123456");
adapter.loginForQQ("sjooguwoersdfjhasjfsa");
adapter.loginForWechat("slfsjoljsdo8234ssdfs");
}
}
通过这么简单的适配,完成了代码兼容。当然,我们代码还可以更加优雅,根据不同的登录方式,创建不同的Adapter。首先,创建ILoginAdapter接口:
public interface ILonginAdapter{
boolean support(Object object);
ResultMsg login(String id,Object adapter);
}
然后,创建一个抽象类AbstraceAdapter继承PassportService原有的功能,同时实现ILoginAdapter接口
public abstract class AbstraceAdapter extends PassportService implements ILoginAdapter {
protected ResultMsg loginForRegist(String username, String password){
if(null == password){
password = "THIRD_EMPTY";
}
super.regist(username,password);
return super.login(username,password);
}
}
然后分别实现不同的登录适配,QQ登录LoginForQQAdapter:
public class LoginForQQAdapter extends AbstraceAdapter{
public boolean support(Object adapter) {
return adapter instanceof LoginForQQAdapter;
}
public ResultMsg login(String id, Object adapter) {
if(!support(adapter)){return null;}
//accesseToken
//time
return super.loginForRegist(id,null);
}
}
手机号登录LoginForTelAdapter类:
public class LoginForTelAdapter extends AbstraceAdapter{
public boolean support(Object adapter) {
return adapter instanceof LoginForTelAdapter;
}
public ResultMsg login(String id, Object adapter) {
return super.loginForRegist(id,null);
}
}
Token自动登录LoginForTokenAdapter类:
public class LoginForTokenAdapter extends AbstraceAdapter {
public boolean support(Object adapter) {
return adapter instanceof LoginForTokenAdapter;
}
public ResultMsg login(String id, Object adapter) {
return super.loginForRegist(id,null);
}
}
微信登录LoginForWechatAdapter类:
public class LoginForWechatAdapter extends AbstraceAdapter{
public boolean support(Object adapter) {
return adapter instanceof LoginForWechatAdapter;
}
public ResultMsg login(String id, Object adapter) {
return super.loginForRegist(id,null);
}
}
然后,创建适配器PassportForThirdAdapter类,实现目标接口IPassportForThird完成兼容
public class PassportForThirdAdapter implements IPassportForThird {
public ResultMsg loginForQQ(String openId) {
return processLogin(openId, LoginForQQAdapter.class);
}
public ResultMsg loginForWechat(String openId) {
return processLogin(openId, LoginForWechatAdapter.class);
}
public ResultMsg loginForToken(String token) {
return processLogin(token, LoginForTokenAdapter.class);
}
public ResultMsg loginForTelphone(String phone, String code) {
return processLogin(phone, LoginForTelAdapter.class);
}
private ResultMsg processLogin(String id,Class<? extends ILoginAdapter> clazz){
try {
ILoginAdapter adapter = clazz.newInstance();
if (adapter.support(adapter)){
return adapter.login(id,adapter);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
客户端测试代码:
public class Test {
public static void main(String[] args) {
IPassportForThird adapter = new PassportForThirdAdapter();
adapter.loginForQQ("sdfasdfasfasfas");
}
}
至此,我们在遵循开闭原则的前提下,完整地实现了一个兼容多平台的业务场景。当然,我目前的这个设计也并不完美,仅供参考,感兴趣的小伙伴可以继续完善这段代码。例如适配器中的参数目前是写死为String,改为Object[]应该更合理。
学习到这里,相信小伙伴有一个疑问了:适配器模式跟策略模式好像区别不大?在这里我要强调一下,适配器模式主要的是功能兼容问题,单场景适配大家可能不会和策略模式有对比。但多场景适配大家产生联想和混淆了。其实,大家有没有发现一个细节,我给每个适配器都加上了一个support()方法,用来判断是否兼容,support()方法参数也是Object的,而support()来自于接口。适配器的实现逻辑并不依赖于接口,我们完全可以将ILoginAdapter接口去掉。而加上接口,只是为了代码规范。上面的代码可以说是策略模式、简单工厂和适配器的综合运用。
Spring中适配器模式也应用的非常广泛,例如:SpringAOP中的AdvisorAdapter类,它有三个实现类MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter和ThrowsAdviceadapter,先来看顶层AdvisorAdapter的源代码:
再看MethodBeforeAdviceAdapter类:
其它两个类这里就不把代码贴出来了。Spring会根据不同的AOP配置来确定使用对应的Advice,跟策略模式不同的一个方法可以同时拥有多个Advice。
下面再来看一个SpringMVC中的HandlerAdapter类,它也有多个子类,类图如下:
其适配调用的关键代码还是在DispatcherServlet的doDispatch()方法中,下面我们还是来看源代码:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
在doDispatch()方法中调用了getHandlerAdapter()方法,来看代码:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
Iterator var2 = this.handlerAdapters.iterator();
while(var2.hasNext()) {
HandlerAdapter adapter = (HandlerAdapter)var2.next();
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
在getHandlerAdapter()方法中循环调用了supports()方法判断是否兼容,循环迭代集合中的Adapter又是在初始化时早已赋值。这里我们不在深入,后面的源码专题中还会继续讲解。
装饰器和适配器模式都是包装模式(Wrapper pattern),装饰器也是一种特殊的代理模式。
装饰器模式 | 适配器模式 | |
---|---|---|
形式 | 是一种非常特别的适配器模式 | 没有层级关系,装饰器模式有层级关系 |
定义 | 装饰器和被装饰器都实现同一个接口,主要目的是为了扩展之后依旧保留OOP关系 | 适配器和被适配者没有必然的联系,通常是采用继承或代理的形式进行包装 |
关系 | 满足is-a的关系 | 满足has-a的关系 |
功能 | 注重覆盖 | 注重兼容、转换 |
设计 | 前置考虑 | 后置考虑 |
优点:
1、能提高类的透明性和复用,现有的类复用但不需要改变。
2、目标类和适配器类解耦,提高程序的扩展性。
3、在很多业务场景中符合开闭原则。
缺点:
1、适配器编写过程需要全面考虑,可能会谧加系统的复杂性。
2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。