概念
适配器模式(Adapater Pattern)是指将一个类的接口转换成用户期望的另一个接口,使原本接口不兼容的类可以一起工作,属于结构型设计模式。
场景
场景一
已经存在的类的方法和需求不匹配(方法结果相同或者相似)的情况
场景二
适配器模式不是软件设计初始阶段考虑的设计模式,是随着软件的发展,由于不同的产品、不同的厂家造成功能类似而接口不同的问题的解决方法,有点亡羊补牢的感觉。
生活中电源插座转接口,电脑视频输出的转接头,手机充电转接头,笔记本电源(适配器)
案例一
国内民用电都是220V交流电,但手机锂电池使用的是5V直流电。因此,我们给手机充电时需要使用电源适配器来进行转换。下面用代码实现这个生活场景:
package com.hanker.mythread;
//模拟220v电源
class AC220{
public int outputAC220V() {
int output = 220;
System.out.println("输出交流电:"+output+"v");
return output;
}
}
//模拟手机充电器
interface DC5{
int outputDC5V();
}
//电源适配器类
class PowerAdapter implements DC5{
private AC220 ac220; //这是原接口
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
@Override
public int outputDC5V() {
int adapaterInput = ac220.outputAC220V();//220V
//转换..变压器
int adapterOutput = adapaterInput / 44;
System.out.println("使用PowerAdapter输入AC:"+adapaterInput+"v,输出DC:"+adapterOutput+"v");
return adapterOutput;
}
}
public class AdapterPatternDemo1 {
public static void main(String[] args) {
AC220 ac220 = new AC220();
DC5 dc5 = new PowerAdapter(ac220);
int dc52= dc5.outputDC5V();
System.out.println("适配器转换后的电压:"+dc52+"v");
}
}
案例二
老系统开发都有登录接口,但是随着业务的发展,单纯地依赖用户名和密码登录不能满足用户的需求了。现在大部分系统都已经支持多种登录方式如: QQ登录、微信登录、手机号登录、微博登录等,同时保留用户名和密码登录的方式。虽然登录形式丰富了,但是登录后台的处理逻辑可以不改,同样是将登录状态保存到session,遵循开闭原则,下面是代码实现。
创建统一的返回结果类 ResultMsg:
package com.hanker.mythread;
public class ResultMsg {
private int code;
private String msg;
private Object data;
public ResultMsg(int code, String msg, Object data) {
super();
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;
}
}
假设老系统的登录代码如下:
package com.hanker.mythread;
public class SiginService {
/**
* 注册丰富
* @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类:
package com.hanker.mythread;
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;
}
}
再创建新的类继承原来的代码:
package com.hanker.mythread;
//稳定的方法直接继承下来
public class SiginForThirdService extends SiginService {
public ResultMsg loginForQQ(String openId) {
//1.openId 是全局唯一的,我们可以把它当做一个用户名
//2.密码默认为QQ_EMPTY
//3.注册(在原系统里创建一个用户)
//4.调用原来的登录方法
return loginForRegist(openId,null);
}
public ResultMsg loginForWechat(String token) {
//通过token获取用户信息,然后重新登录一次
return null;
}
public ResultMsg loginForTelphone(String telphone,String code) {
return null;
}
public ResultMsg loginForRegist(String username,String password) {
super.regist(username, password);
return super.login(username, password);
}
}
客户端测试代码
package com.hanker.mythread;
public class AdapaterPatternDemo2 {
public static void main(String[] args) {
SiginForThirdService service = new SiginForThirdService();
//不改变原来代码,也能够兼容新的需求,还可以再加一层策略模式
ResultMsg resultMsg = service.loginForQQ("1212esadfasdfasdfas");
System.out.println(resultMsg);
}
}
通过一个简单的过程,就完成了代码的兼容,当然,我们的代码还可以更加优雅,根据不同的登录方式创建不同的“Adapter”。
首先创建LoginAdapter接口:
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
public interface LoginAdapter {
boolean support(Object adapter);
ResultMsg login(String id,Object adapter);
}
然后,分别实现不同的登录方式,QQ登录LoginForQQAdapter如下:
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
public class LoginForQQAdapter implements LoginAdapter{
@Override
public boolean support(Object adapter) {
return adapter instanceof LoginForQQAdapter;
}
@Override
public ResultMsg login(String id, Object adapter) {
return null;
}
}
新浪微博登录LoginForSinaAdapter如下:
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
public class LoginForSinaAdapter implements LoginAdapter {
@Override
public boolean support(Object adapter) {
return adapter instanceof LoginForSinaAdapter;
}
@Override
public ResultMsg login(String id, Object adapter) {
return null;
}
}
手机号登录 LoginForTelAdapter如下:
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
public class LoginForTelAdapter implements LoginAdapter {
@Override
public boolean support(Object adapter) {
return adapter instanceof LoginForTelAdapter;
}
@Override
public ResultMsg login(String id, Object adapter) {
return null;
}
}
Token自动登录LoginForTokenAdapter如下
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
public class LoginForTokenAdapter implements LoginAdapter{
@Override
public boolean support(Object adapter) {
return adapter instanceof LoginForTokenAdapter;
}
@Override
public ResultMsg login(String id, Object adapter) {
return null;
}
}
微信登录 LoginForWechatAdapter 如下:
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
public class LoginForWechatAdapter implements LoginAdapter{
@Override
public boolean support(Object adapter) {
return adapter instanceof LoginForWechatAdapter;
}
@Override
public ResultMsg login(String id, Object adapter) {
return null;
}
}
接着创建第三方登录兼容接口IPassportForThird:
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
public interface IPassportForThird {
/**
* QQ登录
* @param id
* @return
*/
ResultMsg loginForQQ(String id);
/**
* 微信登录
* @param id
* @return
*/
ResultMsg loginForWechat(String id);
/**
* 手机号登录
* @param id
* @return
*/
ResultMsg loginForTelphone(String id);
/**
* 记住登录状态后自动登录
* @param id
* @return
*/
ResultMsg loginForToken(String id);
/**
* 注册后自动登录
* @param id
* @return
*/
ResultMsg loginForRegist(String id);
}
实现兼容PassportForThirdAdapter:
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
import com.hanker.mythread.SiginService;
//第三方登录自由适配
public class PassportForThirdAdapter extends SiginService implements IPassportForThird {
@Override
public ResultMsg loginForQQ(String id) {
return processLogin(id, LoginForQQAdapter.class);
}
@Override
public ResultMsg loginForWechat(String id) {
return processLogin(id, LoginForWechatAdapter.class);
}
@Override
public ResultMsg loginForTelphone(String id) {
return processLogin(id, LoginForTelAdapter.class);
}
@Override
public ResultMsg loginForToken(String token) {
return processLogin(token, LoginForTokenAdapter.class);
}
public ResultMsg loginForRegist(String username,String passport) {
super.regist(username, null);
return super.login(username, null);
}
private ResultMsg processLogin(String key,Class<? extends LoginAdapter> clazz) {
try {
LoginAdapter adapter = clazz.newInstance();
if(adapter.support(adapter)) {
return adapter.login(key, adapter);
}else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
客户端测试代码如下:
package com.hanker.mythread.adaper2;
import com.hanker.mythread.ResultMsg;
public class ClientTest {
public static void main(String[] args) {
IPassportForThird passportForThird = new PassportForThirdAdapter();
ResultMsg resultMsg = passportForThird.loginForQQ("xxxx");
System.out.println(resultMsg);
}
}
最后我们看一下类图:
适配器模式在spring中的体现
Spring中适配器模式也应用非常广泛,例如SpringAOP中的AdvisorAdapter类,它有三个实现类: MethodBeforeAdviceAdapter、AfterReturnningAdvisorAdapter、和ThrowsAdvisorAdapter。先看看顶层接口AdvisorAdapter的源代码:
public interface AdvisorAdapter {
boolean supportsAdvice(Advice advice);
MethodInterceptor getInterceptor(Advisor advisor);
}
再看MethodBeforeAdviceAdapter类:
@SuppressWarnings("serial")
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
SpringMVC中的HandlerAdapter类,也有多个子类。
优缺点
优点
①能提高类的透明性和复用性,现有的类会被复用但不需要改变
②目标类和适配器类解耦,可以提高程序的扩展性
③在很多业务场景中符合开闭原则
缺点
①在适配器代码编写过程中需要进行全面考虑,可能会增加系统的复杂性
②增加了代码的阅读难度,降低了代码的可读性,过多使用适配器会使系统的代码变得凌乱