适配器模式 Adapter Pattern
适配器模式概述
- 什么是适配器模式:假设想要调用A类中的某个方法,但是该方法需要一个B类型的参数,而A类中只能拿到A类型的参数,将A类型的参数合理的转换为B类型的参数,这个转换过程就可以成为适配,总结来说: 让原本不能匹配的接口可以协同工作
- 适配器模式属于接口模式,注意分为三类: 类适配器, 对象适配器, 接口适配器
一. 类适配器模式
案例: 给手机充电,手机为调用方,充电器为适配器接口, 输出220v电压插座为被支配者接口
实现要点
通过创建中间适配器类,进行适配,手机充电需要调用5V接口,而现在只有输出220V的Socket, 创建中间适配器接口,创建适配器类,适配器类继承输出220V的Socket,可以拿到输出220V的方法,并且实现适配器接口Ivoltage5v,重写抽象方法,在方法中调用Socket输出220v的方法,降压后输出
代码示例
- 被适配方
//被适配方,输出220V电压
class Socket{
//该接口输出220V电压
public Integer outPut220v(){
System.out.println("输出220V电");
return 220;
}
}
- 适配接口
//适配接口
interface Ivoltage5v {
public Integer output5v();
}
- 适配器
//适配器继承被适配方Socket,
class VoltageAdapter extends Socket implements Ivoltage5v{
//适配方法,通过继承可以获取到Socket输出220的方法,通过
//适配方法降压为5v输出
@Override
public Integer output5v() {
Integer srcV = super.outPut220v();
System.out.println("通过类适配器降压");
return srcV/44;
}
}
- 调用方
//调用方: 手机充电
class Phone{
//调用充电方法,但是输出220V的不适用
public void charging(Ivoltage5v voltager) {
Integer v = voltager.output5v();
System.out.println("给手机充电"+v);
}
}
- 测试
public class AdapterTest {
public static void main(String[]args) {
Phone p = new Phone();
Ivoltage5v voltager = new VoltageAdapter();
p.charging(voltager);
}
}
分析类适配器模式
- 对类适配器模式我的理解是: 创建适配接口Ivoltage5v ,提供适配方法output5v(),创建适配器类VoltageAdapter 实现适配接口,并继承定义了需要调用但不适配的方法的类Socket ,需要调用Socket 中的 outPut220v()方法,但是该方法返回的参数是220,重写适配接口中的适配方法output5v(),在方法中通过子类继承父类的关系,使用super调用Socket 中原本不适用的方法,获取该方法返回的值,然后在适配方法中根据需求对父类方法返回的值进行修改适配,在后续调用时直接调用适配方法即可
- 通过继承被适配方,可以拿到被适配方的方法,进行重写被适配比较灵活
- 被适配方与适配器适配后的方法都需要暴露出来运行外部继承调用
- java是单继承,通过类适配器进行适配需要继承被适配方,拿到原有的需要进行适配的方法,局限性
二. 对象适配器模式
什么是对象适配器
根据"合成复用原则" 在类适配器模式上进行修改,使原本创建中间适配器不再通过继承方法获取原有被适配方的方法,而是通过适配器中持有聚合一个被适配方的对象,实现适配接口时通过持有的被适配对象调用需要适配的方法,解决了类适配器模式需要适配器继承被适配者的局限性(也就是在适配器类VoltageAdapter 中,定义一个被适配对象的属性Socket,重写适配方法时通过该属性对象调用需要适配的方法)
代码示例
修改原有适配器,将通过继承模式拿到被适配者,修改为:适配者持有聚合一个被适配者,通过聚合进来的被适配者拿到需要适配的方法
- 适配器
//适配器持有被适配者
class VoltageAdapter implements Ivoltage5v{
private Socket socket;
//通过构造器或者提供set方法,对持有的Socket赋值
public VoltageAdapter(Socket socket) {
this.socket = socket;
}
//适配方法:通过持有的socket实例,调用被适配者方法
@Override
public Integer output5v() {
Integer srcV = socket.outPut220v();
System.out.println("通过类适配器降压");
return srcV/44;
}
}
- 测试
public class AdapterTest {
public static void main(String[]args) {
Phone p = new Phone();
//适配者持有被适配者,创建被适配者对象
Socket socket = new Socket();
//通过VoltageAdapter构造器赋值给注入进来的被适配者
Ivoltage5v voltager = new VoltageAdapter(socket);
p.charging(voltager);
}
}
三. 接口适配器模式
什么是接口适配器模式: 假设接口中的某个方法不适用,需要重写,但是并不希望全部重写,在调用时除了指定的方法外,其它方法还希望使用接口中原有的,创建一个实现该接口的抽象子类,抽象子类中对于不适配的方法进行重写,在调用的该不适用的方法时使用抽象子类去调用,或者不重写,在使用到该方法时通过创建抽象类对象,在{中进行重写},例如m1,与m2方法
代码示例
- 接口
interface AdapterInterface{
//假设接口中的m1方法并不适用
void m1();
void m2();
void m3();
}
- 创建抽象类,抽象类继承接口
abstract class AbsAdapter implements AdapterInterface{
//假设接口中的m1方法并不适用
//@Override
//public void m1() { };
@Override
public void m2() {
System.out.println("m2方法执行");
};
@Override
public void m3() { };
}
- 在调用m1方法时可以通过匿名类的方式冲向m1方法,根据需求自己实现
public class AdapterTest {
public static void main(String[]args) {
AbsAdapter a = new AbsAdapter() {
//重写AbsAdapter中的m1方法
@Override
public void m1() {
System.out.println("接口适配器模式");
}
};
//m1方法为重写的方法
a.m1();
//m2方法为抽象类中的
a.m2();
}
}
Spring框架适配器模式适用案例
- 在SpringMVC中的 HandlerAdapter,
- 复习SpringMVC: Spring的web.xml中有DispathcerServlet前端控制器,该类中加载了关于mvc的xml配置文件,并指定拦截所有请求,将所有请求都交给DispatcherServlet,通过DispatcherServlet初始化servlet 截获符合特定格式的URL请求。初始化DispatcherServlet上下文对应的WebApplicationContext,并将其与业务层、持久化层的WebApplicationContext建立关联。初始化Spring MVC的各个组成组件,并装配到DispatcherServlet中
- SpringMVC处理一次请求的执行流程: 用户发送请求,创建DispathcerServlet前端控制器对象,将所有请求交给该对象处理,通过DispathcerServlet对象将请求分发给处理器HandlerMapping,通过HandlerMapping进行映射请求地址与Controller方法, DispathcerServlet将请求交给Controller,Conrtoller进行业务处理后,将处理结果存入ModelAndView对象中, DispatcherServlet查询多个ViewResoler视图解析器,找到对应的ModelAndView指定视图,将视图返回给用户,(发送请求时会通过DispathcerServlet对象调用doService方法,该方法中会根据请求路径获取处理器映射器HandlerMapping,HandlerMapping对象调用getHandler()方法,获取到到对应的Handler(也就是请求路径对应的controller方法),如果不存在则调用sendError方法,报404,存在则通过Handler对象调用handle()方法,执行路径对应的controller方法,执行完毕后,会通过ViewResolver视图转换器(视图解析器Springmvc配置文件中的),找到对应的ModleandView返回给客户端)
- 在获取处理器映射器HandlerMapping时就是通过适配器模式设计的,HandlerMapping调用getHandler()方法返回的数据会通过getHandlerAdapter()方法与自己的进行适配,适配不成功则返回false
- 在SpringAOP中的AdvisorAdapter接口就是一个适配器,该接口中的“MethodInterceptor getInterceptor(Advisor var1)”方法就是用来适配的方法,在该接口的实现类中,例如MethodBeforeAdviceAdapter中,对该方法进行了重写
- 在jpa中 JpaVendorAdapter ,该接口也是一个适配器,该接口中的方法通过子类重写进行适配,跟据连接数据库的不同适配进行不同的适配
业务与设计模式落地示例
- 在很多网站上,用户可以通过第三方授权登录(如微信、QQ、微博等)来进行注册和登录。每种第三方授权登录都有自己的API,因此需要使用适配器设计模式来封装不同授权登录的API,以适应系统的登录接口
- 定义抽象接口,表示系统对外提供的登录接口。这里假设系统提供了一个统一的登录接口login(String username, String password)来实现用户登录功能
public interface AuthService {
boolean login(String username, String password);
}
- 实现一个第三方登录适配器,用于将各个第三方登录API进行适配,其中ThirdPartyAuthService表示第三方登录服务,其内部实现了具体的第三方登录API
public class ThirdPartyAuthAdapter implements AuthService {
private ThirdPartyAuthService thirdPartyAuthService;
public ThirdPartyAuthAdapter(ThirdPartyAuthService thirdPartyAuthService) {
this.thirdPartyAuthService = thirdPartyAuthService;
}
@Override
public boolean login(String username, String password) {
// 调用第三方登录API进行用户认证
return thirdPartyAuthService.auth(username, password);
}
}
- 对外接口示例
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
// 创建第三方登录适配器实例
ThirdPartyAuthService qqAuthService = new QQAuthService();
AuthService authService = new ThirdPartyAuthAdapter(qqAuthService);
// 使用适配器进行登录
boolean result = authService.login("user_name", "password");
if (result) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
}
}