设计模式-适配器模式
定义
适配器模式(Adapter Pattern)又叫做变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作.
属于结构型设计模式.
也就是说,当前系统存在两种接口A和B,客户只支持访问A接口,但是当前系统没有A接口对象,但是有B接口对象,但客户无法识别B接口,因此需要通过一个适配器C,将B接口内容转换成A接口,从而使得客户能够从A接口获取得到B接口内容.
在软件开发中,基本上 任何问题都可以通过增加一个中间层进行解决.适配器模式就是一个中间层.综上,适配器模式其实起着转化/委托的作用,将一种接口转化为另一种符合需求的接口.
适配器模式一般包含3种角色:目标角色、源角色、适配器.
1、目标角色(Target):也就是我们期望的接口;
2、源角色(Adaptee):存在于系统中,内容满足客户需求(需转换),但借口不匹配的接口实例;
3、适配器(Adapter):将源角色(Adaptee)转化为目标角色(Target)的类实例.
适配器模式各角色之间的关系如下:
假设当前系统中,客户端需要访问的是target接口,但target接口没有一个实例符合需求,而Adaptee实例符合需求;但是客户端无法直接使用Adaptee(接口不兼容);因此,我们需要一个适配器(Adapter)来进行中转,让Adaptee能转化为Target接口形式.
适配器模式有3种形式:类适配器、对象适配器、接口适配器.
类适配器
类适配器的原理就是通过继承来实现适配器的功能.具体做法:让Adapter实现Target接口,并且继承Adaptee,这样Adapter就具备Target和Adaptee等特性,就可以将两者进行转化.
类适配器UML图下面我们以一个实例进行讲解,来看下该实例分别用类适配器、对象适配器和接口适配器是怎样进行代码实现.在中国民用电都是220V交流电,但我们手机使用的锂电池使用的5V直流电.因此我们给手机充电时就需要使用电源适配器来进行转换.下面我们有代码来还原这个生活场景,创建Adaptee角色,需要被转换的对象AC220类,表示220V交流电:
Adaptee角色:需要被转换的对象AC220类 Target角色:DC5接口,表示5V直流电的标准 Adapter角色:电源适配器PowerAdapter类 测试 运行结果上面的案例中,通过增加PowerAdapter电源适配器,实现了两者的兼容.
对象适配器
对象适配器的原理就是通过组合来实现适配器的功能.具体做法:让Adapter实现Target接口,然后内部持有Adaptee实例,然后在Target接口规定的方法那转换Adaptee.
接口适配器:只实现target接口,并持有Adaptee对象. 测试方法接口适配器
接口适配器的关注点与类适配器和对象适配器不太一样,类适配器和对象适配器着重于系统存在一个角色(Adaptee)转化成目标接口(Target)所需内容,而接口适配器的使用场景是解决接口方法过多,如果直接实现接口,那么类会多出许多空实现的方法,类显得很臃肿.此时,使用接口适配器就能让我们只实现我们需要的接口方法,目标更清晰.
接口适配器的主要原理就是利用抽象类实现接口,并且空实现接口众多方法.下面我们来接口适配器的源码实现,首先创建Target角色DC5类:
Target角色:DC5类(具有多个接口) Adaptee角色:需要被转换的对象AC220类 Adapter角色:PowerAdapter类. 测试代码,运行结果同上.重构第三方登录自由适配的业务场景
下面我们来一个实际的业务场景,利用适配器模式来解决实际问题.年纪稍微大一点的小伙伴一定经历过这样一个过程,我们很早以前开发的老系统应该都有登录接口,但是随着业务的发展和社会的进步,单纯的依赖用户名密码登录显然不能满足用户需求了.现在,我们大部分系统都支持多种登录方式,如QQ登录、微信登录、手机登录、微博登录等等,同时保留用户名密码登录方式.虽然登录方式丰富了,但是登录后的处理逻辑可以不必改,同样是将登陆状态保存到session,遵循开闭原则,我们开始创建统一的返回结果ResultMsg类:
创建统一的返回结果ResultMsg类 老系统的登录逻辑PassportService为了遵循开闭原则,老系统的代码我们不会去修改.那么下面开启代码重构之路,先创建Member类:
用户信息类运行非常稳定的代码我们不去改动,首先创建Target角色IPassportForThird接口:
Target角色IPassportForThird接口:支持了多种登录方式然后创建适配器Adapter角色实现兼容,创建一个新的类PassportForThirdAdapter继承原来的逻辑:
Adapter角色:类PassportForThirdAdapter继承原来的逻辑和新登录方式 测试代码通过这么一个简单的适配,完成了代码兼容.当然,我们代码还可以更加优雅,根据不同的登录方式,创建不同的Adapter.首先,创建ILoginAdapter接口:
LoginAdapter接口然后,创建一个抽象类AbstractAdapter继承PassportService原有的功能,同时实现ILoginAdapter接口,然后分别实现不同的登录适配.
抽象类 QQ登录LoginForQQAdapter 手机登录:LoginForTelAdapter Token自动登录:LoginForTokenAdapter 微信登录:LoginForWechatAdapter然后,创建适配器PassportForThirdAdapter类,实现目标接口IPassportForThird完成兼容,
适配器PassportForThirdAdapter类 测试代码最后我们看一下类图:
UML图(PS:mac本对uml工具支持真的有限啊,哪个大佬给推荐个好用的建模工具,谢过啦~)至此,我们在遵循开闭原则的前提下,完整地实现了一个兼容多平台登录的业务场景.当然,我们目前的这个设计也并不完美,仅供参考,感兴趣的小伙伴们可以继续完善这段代码.例如适配器中的参数目前是写死成String的,改为Object[]会更合理.
学习到这里,相信小伙伴们会有一个疑问:适配器模式跟策略模式好像区别不大?在这里我要强调一下,适配器模式主要解决的是功能兼容问题,单场景适配大家肯定不会和策略模式有对比.但多场景适配大家容易产生联想和混淆.其实,大家有没有发现一个细节,我给每个适配器都加上了一个support()方法用来判断是否兼容,support()方法的参数也是Object的,而support来自于接口.适配器内的逻辑并不依赖于接口,我们完全可以讲ILoginAdapter接口去掉.而加上接口,只是为了代码规范.上面的代码可以说是策略模式、简单工厂和适配器模式的综合运用.
适配器模式在源码中的体现
Spring中适配器模式也应用的非常广泛,例如:SpringAOP中的AdvisorAdapter类,它有三个实现类MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter和ThrowsAdviceAdapter,先来看顶层接口AdvisorAdapter的源代码:
AdvisorAdapter接口再看MethodBeforeAdviceAdapter类:
MethodBeforeAdviceAdapter类 AfterReturningAdviceAdapter类剩余的那个类就不贴了,Spring会根据不同的AOP配置来确定使用对应的Advice,跟策略模式不同的是:策略模式一个方法可以同时拥有多个Advice.
扩展:SpringMVC中的HandlerAdapter类也是一个适配器模式的应用实例.
适用场景
1、已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况.
2、适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案.
生活中也存在非常多的应用场景,例如电源转换插头、手机充电转换头,显示器转接头等.
优点
1、能提高类的透明性和复用性,现有的类复用但不需要改变;
2、目标类和适配器类解耦,提高程序的扩展性;
3、在很多业务场景中符合开闭原则.
缺点
1、适配器编写过程需要全面考虑,可能会增加系统的复杂度;
2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱.
网友评论