适配器模式
适配器模式介绍
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。举个简单的例子,现如今许多智能手机为了减小空间的浪费,逐渐取消了3.5mm圆孔的耳机孔,只保留了一个充电和插耳机通用的type-c孔,那么为了能让我们圆形耳机孔能适配type-c口,我们只需要买一个转接头就能将圆孔转换成type-c口就能正常使用圆孔耳机了。适配器适用于以下几种业务场景:
1、已经存在的类,它的方法和需求不匹配的情况。
2、适配器模式不是软件设计阶段考虑的设计模式,而是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。
适配器模式案例一
下面通过代码展示一下3.5mm圆形耳机插口通过适配器转成type-c的耳机插口,首先编写圆形插口耳机类CircleEarphone
/**
* @author: Winston
* @createTime: 2021/6/28
*
* 圆形耳机,表示只能插圆形的手机
*/
public class CircleEarphone {
public void insertCircle(){
System.out.println("圆形耳机插头,能插入圆形耳机孔");
}
}
目标接口TypeC,也就是客户想要的功能
public interface TypeC {
/**
* 能插入type-c耳机孔的头
*/
public void insertTypeC();
}
圆形插头的适配器Adapter,也就是把圆形插头转成type-c插头的适配器,这里有两种形式实现,第一种是类适配器,第二种是方法适配器。
/**
* @author: Winston
* @createTime: 2021/6/28
*
* 圆形耳机的适配器实现1,类适配器,通过集成和实现接口的方式,将圆形插头->type-c插头
*/
public class CircleAdapter1 extends CircleEarphone implements TypeC {
@Override
public void insertTypeC() {
// 调用父类方法
insertCircle();
System.out.println("使用圆口适配器,将圆形耳机插口转成type-c耳机插口");
}
}
/**
* @author: Winston
* @createTime: 2021/6/28
*
* 圆形耳机的适配器实现二,方法适配器,通过构造方法的形式,将圆形插头->type-c插头
*/
public class CircleAdapter2 implements TypeC {
// 直接关联被适配类
private CircleEarphone circleEarphone;
// 通过构造函数传入具体需要适配的被适配类对象
public CircleAdapter2(CircleEarphone circleEarphone) {
this.circleEarphone = circleEarphone;
}
@Override
public void insertTypeC() {
// 这里是使用委托的方式完成特殊功能
circleEarphone.insertCircle();
System.out.println("使用圆口适配器,将圆形耳机插口转成type-c耳机插口");
}
}
编写测试类typc-c耳机插口的手机:
/**
* @author: Winston
* @createTime: 2021/6/28
*
* 测试类,type-c耳机口的手机
*/
public class TypeCMobile {
public static void main(String[] args) {
System.out.println("==================第一种适配器:类适配器=====================");
CircleAdapter1 adapter1 = new CircleAdapter1();
adapter1.insertTypeC();
System.out.println("我是type-c耳机孔的智能手机,插入成功");
System.out.println("==================第二种适配器:方法适配器=====================");
CircleAdapter2 adapter2 = new CircleAdapter2( new CircleEarphone());
adapter2.insertTypeC();
System.out.println("我是type-c耳机孔的智能手机,插入成功");
}
}
运行结果:
==================第一种适配器:类适配器=====================
圆形耳机插头,能插入圆形耳机孔
使用圆口适配器,将圆形耳机插口转成type-c耳机插口
我是type-c耳机孔的智能手机,插入成功
==================第二种适配器:方法适配器=====================
圆形耳机插头,能插入圆形耳机孔
使用圆口适配器,将圆形耳机插口转成type-c耳机插口
我是type-c耳机孔的智能手机,插入成功
我们来看一下适配器模式的结构图:

在上面这个案例中,TypeCMobile就是途中的Client,TypeC就是客户想要的目标接口Target,它可以是具体或抽象的类,CircleEarphone就是源接口也就是适配者Adaptee,CircleAdapter1(CircleAdapter2)就是适配器,通过适配器将源接口转成目标接口。
适配器模式案例二
在以前的老系统的登录接口都是通过账号和密码登录,现如今单纯的只靠账号和密码登录已经不满足用户的需求了,现在大多数的网站都支持第三方登录,比如QQ登录、微信登录、手机号登录等。现在我们将老系统的登录接口进行扩展,增加多种登录方式,虽然说登录的形式丰富了,但是登录以后的逻辑并不需要修改。先创建一个返回结果ResultMsg
public class ResultMsg {
/**
* 状态码
*/
private int code;
/**
* 结果信息
*/
private String msg;
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;
}
}
假设老系统的登录逻辑 SignInService
/**
* @author: Winston
* @createTime: 2021/6/29
*
* 老系统原来的登录Service
*/
public class SigInService {
/**
* 注册方法
* @param username
* @param password
* @return
*/
public ResultMsg register(String username, String password){
return new ResultMsg(200,"注册成功");
}
/**
* 登录方法
* @param username
* @param password
* @return
*/
public ResultMsg login(String username, String password){
return new ResultMsg(200,"登录成功");
}
}
已有的类不做修改,我们通过创建新的类来继承SignInService完成新登录方式的添加
/**
* @author: Winston
* @createTime: 2021/6/29
*
* 第三方登录方式
*/
public class SignInForThirdService extends SignInService {
public ResultMsg loginByQQ(String openId){
System.out.println("通过QQ登录");
return new ResultMsg(200,"使用QQ登录成功");
}
public ResultMsg loginByWechat(String openId){
System.out.println("通过微信登录");
return new ResultMsg(200,"使用微信登录成功");
}
public ResultMsg loginByMobile(String mobile,String code){
System.out.println("通过手机号登录");
return new ResultMsg(200,"使用手机号登录成功");
}
}
测试:
public class SignInForThirdServiceTest {
public static void main(String[] args) {
String openId = "adjkfjsdo2lkjdsf";
SignInForThirdService signInForThirdService = new SignInForThirdService();
ResultMsg resultMsg = signInForThirdService.loginByWechat(openId);
System.out.println(resultMsg.getCode()+"=======>"+resultMsg.getMsg());
}
}
SignInForThirdService类通过继承SignInService就能不改变原有逻辑同时能够进行登录功能的扩展完成简单的适配,当然我们可以更进一步根据不同的登录方式,创建不同的适配器Adapter,我们将登录案例重新进行优化。
首先是我们原来的登录接口
/**
* @author: Winston
* @createTime: 2021/6/29
*
* 老系统原来的登录Service
*/
public class SignInService {
/**
* 注册方法
* @param username
* @param password
* @return
*/
public ResultMsg register(String username, String password){
return new ResultMsg(200,"注册成功");
}
/**
* 登录方法
* @param username
* @param password
* @return
*/
public ResultMsg login(String username, String password){
return new ResultMsg(200,"登录成功");
}
}
接下来将我们要实现的几种第三方登录方式放在一个接口中,也就是我们要扩展的那几种方式。
/**
* 只扩展
* Created by Tom on 2019/3/16.
*/
public interface ISignInForThird {
/**
* QQ登录
* @param id
* @return
*/
ResultMsg loginForQQ(String id);
/**
* 微信登录
* @param id
* @return
*/
ResultMsg loginForWechat(String id);
/**
* 手机号登录
* @param mobile
* @param code
* @return
*/
ResultMsg loginForMobile(String mobile, String code);
}
编写适配器类SignInForThirdAdapter,同样也是继承原来的Service类SignInService同时实现未实现的接口ISignInForThird
/**
* @author: Winston
* @createTime: 2021/6/29
*/
public class SignInForThirdAdapter extends SignInService implements ISignInForThird {
@Override
public ResultMsg loginForQQ(String id) {
return null;
}
@Override
public ResultMsg loginForWechat(String id) {
return null;
}
@Override
public ResultMsg loginForMobile(String mobile, String code) {
return null;
}
}
SignInForThirdAdapter类是为了适配ISignInForThird接口中的几种登录方式,是个大的适配器,我们可以分别为不同的登录方式单独创建适配器,在这里不论是QQ登录适配器、还是微信登录适配器或者是手机号登录适配器它们都有通用方法,因此我们创建一个LoginAdapter接口,所有第三方适配器都可以实现该接口,当然这一步并不是必须的,这个接口可有可无。
/**
* @author: Winston
* @createTime: 2021/6/29
*
* 第三方登录适配器
*/
public interface LoginAdapter {
/**
* 登录方法
* @param params 登录需要的参数
* @return
*/
ResultMsg login(String... params);
}
紧接着我们分别写QQ登录适配器、微信登录适配器和手机登录适配器
/**
* @author: Winston
* @createTime: 2021/6/29
*
* QQ登录适配器
*/
public class QQLoginAdapter implements LoginAdapter{
/**
* 使用QQ登录方法
* @param params
* @return
*/
@Override
public ResultMsg login(String... params){
System.out.println("使用QQ账号登录系统");
return new ResultMsg(200,"使用QQ账号登录系统成功");
}
}
/**
* @author: Winston
* @createTime: 2021/6/29
* <p>
* 微信登录适配器
*/
public class WechatLoginAdapter implements LoginAdapter {
/**
* 使用微信登录方法
*
* @param params
* @return
*/
@Override
public ResultMsg login(String... params) {
System.out.println("使用微信账号登录系统");
return new ResultMsg(200,"使用微信账号登录系统成功");
}
}
/**
* @author: Winston
* @createTime: 2021/6/29
* <p>
* 手机号登录适配器
*/
public class MobileLoginAdapter implements LoginAdapter {
/**
* 使用手机号验证码登录方法
*
* @param params
* @return
*/
@Override
public ResultMsg login(String... params) {
System.out.println("使用手机号和验证码登录系统");
return new ResultMsg(200,"使用手机号验证码登录系统成功");
}
}
将SignInForThirdAdapter中的方法补充完整
/**
* @author: Winston
* @createTime: 2021/6/29
*/
public class SignInForThirdAdapter extends SignInService implements ISignInForThird {
@Override
public ResultMsg loginForQQ(String id) {
return this.loginProcess(QQLoginAdapter.class,id);
}
@Override
public ResultMsg loginForWechat(String id) {
return this.loginProcess(WechatLoginAdapter.class,id);
}
@Override
public ResultMsg loginForMobile(String mobile, String code) {
return this.loginProcess(MobileLoginAdapter.class,mobile,code);
}
/**
* 登录流程,这里用到了简单工厂模式及策略模式
* @param loginAdapterClass 适配器class
* @param params 参数
* @return
*/
private ResultMsg loginProcess(Class<? extends LoginAdapter> loginAdapterClass, String... params) {
// 首先根据class查找合适的适配器类型
try {
LoginAdapter adapter = loginAdapterClass.newInstance();
return adapter.login(params);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return new ResultMsg(500,"登录失败");
}
}
适配器模式小结
优点:
- 可以让任何两个没有关联的类一起运行
- 提高了类的复用,现有的类复用不需要改变
缺点:
- 过多的使用适配器会让系统非常凌乱