概念
适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
适配器的实现有两种方式:类适配器和对象适配器。其中,类适配器是通过继承关系来实现,对象适配器使用组合关系来实现。
使用方式
关键点:
- 定义适配器接口;
- 定义适配器实现类,实现适配器接口;
假设有发送短信的场景,有阿里和网易的发送通道接口。假设在设计之初,阿里和网易的发送接口分别只有单发和群发的接口,那么此时就可以利用适配器模式将发送接口统一。
- 阿里的发送接口
public class SmsForAli {
public void sendSms(String mobile,String content) {
System.out.println("发生短信,调用阿里的接口,mobile=" +mobile);
}
}
- 网易的发送接口
public class SmsForWangyi {
public void sendSms(List<String> mobileList, String content) {
System.out.println("发生短信,调用网易的发送接口,content=" +content);
}
}
在没有适配器之前,调用方需要知道所有短信渠道的发送接口,且知道他们的方法签名。此时是这样调用的。
@Test
public void testSmsOrigin() {
// 使用阿里的发送短信
SmsForAli smsForAli = new SmsForAli();
smsForAli.sendSms("123456","hahaha");
// 使用网易的发生短信
SmsForWangyi smsForWangyi = new SmsForWangyi();
smsForWangyi.sendSms(Arrays.asList("123456"),"hahahha");
}
定义适配器接口
/**
* 定义适配类的方法
*/
public interface SmsIAdaptor {
// 单发
void sendSms(String mobile,String content);
// 群发
void sendSms(List<String> mobileList, String content);
}
/**
* 短信通道
*/
public enum SmsWay {
ALi,
WANG_YI
}
类适配器
- 阿里适配器的实现类
/**
* 阿里短信的适配器
*/
public class SmsAliAdaptor extends SmsForAli implements SmsIAdaptor {
@Override
public void sendSms(String mobile, String content) {
System.out.println("开始适配,调用父类方法");
super.sendSms(mobile, content);
}
@Override
public void sendSms(List<String> mobileList, String content) {
for(String mobile : mobileList) {
sendSms(mobile,content);
}
}
}
- 网易短信的适配器实现类
/**
* 网易短信的适配·
*/
public class SmsWangyiAdaptor extends SmsForWangyi implements SmsIAdaptor {
@Override
public void sendSms(String mobile, String content) {
this.sendSms(Collections.singletonList(mobile),content);
}
@Override
public void sendSms(List<String> mobileList, String content) {
System.out.println("开始适配");
super.sendSms(mobileList,content);
}
}
- 定义短信适配器工厂
public class SmsFactory {
public static SmsIAdaptor getSmsAdaptor(SmsWay smsWay) {
switch (smsWay) {
case ALi:
return new SmsAliAdaptor();
case WANG_YI:
return new SmsWangyiAdaptor();
default:
throw new RuntimeException("way error");
}
}
}
- 使用方式
/**
* 类适配器测试
*/
@Test
public void testSmsAdaptorForClazz() {
// 使用阿里适配器发送短信
SmsIAdaptor smsIAdaptor = SmsFactory.getSmsAdaptor(SmsWay.ALi);
smsIAdaptor.sendSms("123456","阿里通道发生短信");
// 使用网易通道发生短信
smsIAdaptor = SmsFactory.getSmsAdaptor(SmsWay.WANG_YI);
smsIAdaptor.sendSms("123456","网易通道发生短信");
}
在测试方法中可以看出,调用者只需要认识适配器的签名以及有哪些发送通道即可。无需像没接适配器的时候,需要认识多个渠道的短信接口签名。
对象适配器
对象适配器也是类似的,其实就是把继承方式改为组合方式去实现。
- 阿里适配器实现类
public class SmsAliAdaptor implements SmsIAdaptor {
private SmsForAli smsForAli;
public SmsAliAdaptor(SmsForAli smsForAli) {
this.smsForAli = smsForAli;
}
@Override
public void sendSms(String mobile, String content) {
smsForAli.sendSms(mobile,content);
}
@Override
public void sendSms(List<String> mobileList, String content) {
for (String mobile : mobileList) {
smsForAli.sendSms(mobile,content);
}
}
}
- 网易适配器实现类
public class SmsWangyiAdaptor implements SmsIAdaptor {
private SmsForWangyi smsForWangyi;
public SmsWangyiAdaptor(SmsForWangyi smsForWangyi) {
this.smsForWangyi = smsForWangyi;
}
@Override
public void sendSms(String mobile, String content) {
sendSms(Arrays.asList(mobile),content);
}
@Override
public void sendSms(List<String> mobileList, String content) {
smsForWangyi.sendSms(mobileList, content);
}
}
- 短信适配器的工厂
public class SmsFactory {
public static SmsIAdaptor getSmsAdaptor(SmsWay smsWay) {
switch (smsWay) {
case ALi:
return new SmsAliAdaptor(new SmsForAli());
case WANG_YI:
return new SmsWangyiAdaptor(new SmsForWangyi());
default:
throw new RuntimeException("error");
}
}
}
- 对象适配器的使用方式
@Test
public void testSmsAdaptorForObject() {
// 使用阿里适配器发送短信
SmsIAdaptor smsIAdaptor = structure.adapter.object.SmsFactory.getSmsAdaptor(SmsWay.ALi);
smsIAdaptor.sendSms("123456","阿里通道发生短信");
// 使用网易通道发生短信
smsIAdaptor = structure.adapter.object.SmsFactory.getSmsAdaptor(SmsWay.WANG_YI);
smsIAdaptor.sendSms("123456","网易通道发生短信");
}
总结
适配器模式通常用于补偿的场景,用来补救接口上的缺陷。在Java日志中也有着适配器模式的应用。
由于Java日志并没有规范,在开发中有很多类似的框架,提供了不同的日志级别来打印信息,其中,比较常用的有 log4j、logback,以及 JDK 提供的 JUL(java.util.logging) 和 Apache 的 JCL(Jakarta Commons Logging) 等。
当项目中存在多个日志框架时,由于每个框架的配置方式都不一致,这样将导致项目日志的管理变得复杂。为了解决这个问题,Slf4j 日志就是应用了适配器模式来统一这些日志规范的。
Slf4j 并没有具体的实现,只定义了接口,也就是规范,具体的实现就是log4j、logback这些框架。Slf4j 不仅仅提供了统一的接口定义,还提供了针对不同日志框架的适配器。对不同日志框架的接口进行二次封装,适配成统一的 Slf4j 接口定义。在具体的应用中,只需要获取对应的日志实现即可(如log4j、logback)