题外话
最近在系统性整理和输出之前的知识点(简言之:准备面试🤡)。整理设计模式这一块,最大的感触是随着阅读代码量的增加和接触的业务场景变多,很多设计模式理解起来不再那么别扭。从项目工程管理的角度来看最直观,优雅?不,“实在是妙啊”
概述
适配器的核心思想是什么,无非就是适配器实现目标接口,同时持有待适配对象的引用,目标方法的具体实现则通过持有的待适配对象来处理。别划走,我来说人话~
众所周知,接口既是规范、协议。你作为老板定义了一套规范(接口),然后工人按照规范做事(接口实现类),一切安好。现在来了个老外(待适配对象),看不懂你的规范,同时你又需要将它纳入到你的体系中,怎么做?找个人(适配器)帮他翻译,但具体的活还得老外自己干。
宇宙惯例,举个插头适配器的例子,这个例子我几年前写过,现在回来再看,之前写的乱七八糟,基于现有的理解又重新写了一版。很多设计模式的学习过程我都有这种感受,可能这就是“常读常新”吧
场景1
中国小伙去美国旅游,手机需要充电
// 1.美国插头工业标准接口
public interface USPlug {
void getPower();
}
// 2.按照工业标准制作的美国插头
public class USPlugImpl implements USPlug {
@Override
public void getPower() {
System.out.println("getting power success");
}
}
// 3.美国的插座
public class USSocket {
// 只能插入美国标准的插头(合情合理~)
public void plugin(USPlug usPlug) {
usPlug.getPower();
}
}
// 4.中国小伙的插头(CNPlug 是中国插头的工业标准)
public class CNPlugImpl implements CNPlug {
@Override
public void tongDian() {
System.out.println("插头通电了");// 中国插头说中文,合情合理
}
}
工业标准不同,中美插头样式也不一样,美国插座只能插入美国行业标准(USPlug接口)的插头,中国小伙手机没电已成定局…
适配器模式救场:
// 5.适配器实现了美国插头的接口规范,必然可以使用美国插座
public class PlugAdapter implements USPlug {
@Resource
private CNPlug cnPlug;
public PlugAdapter() {}
public PlugAdapter(CNPlug cnPlug) {// 通过构造函数持有待适配的对象
this.cnPlug = cnPlug;
}
@Override
public void getPower() {
cnPlug.tongDian();// 给国产插头通电(接口的方法交由待适配的对象完成)
}
}
// 6.测试
@SpringBootTest
public class AdapterDemo1Test {
@Resource
USPlug usPlug;
@Resource
USSocket usSocket;
@Resource
USPlug plugAdapter;
@Test
public void test() {
usSocket.plugin(usPlug);// 美国插头
usSocket.plugin(plugAdapter);//适配器插头
}
}
// 7.输出
getting power success
插头通电了
场景2
如果你经常接触第三方SDK的接入,或者新系统接入老系统功能,你很快就能体会适配器模式、门面模式的含义,至于模式的精髓、精通? 曾经我也…最近“常读常新”的感受让我无地自容🫣
// 做过保险相关的SaaS服务,其中有一块功能是做数据清洗,
// 大体就是把各大保司格式不同的保全数据处理成统一格式的对象
// 1.定义清晰接口
public interface IDataCleaning {
String cleaning(IMaintenanceInfo maintenanceInfo);
}
// 2.如果我们自己能控制数据,那太简单了
// 传入对象,进行处理(话说如果能决定传入的对象,也就没有数据清洗这个需求了)
public class IDataCleaningImpl implements IDataCleaning {
@Override
public String cleaning(IMaintenanceInfo maintenanceInfo) {
maintenanceInfo.getMaintenanceInfo();
return "success";
}
}
// 3.对接的各大保司提供的SDK都有获取保全的方法,但格式肯定是不统一的,比如A公司:
public class PARSSdk {
public String printMainInfo() {
System.out.println("平安人寿 保全信息");
return null;
}
}
// B公司:
public class PiccSdk {
public String getDataInfo() {
System.out.println("PICC 获取保全信息");
return null;
}
}
// 4.别人的SDK我们改不了,那我们就进行适配:适配器实现我们的IDataCleaning接口
// A公司获取保全数据的适配器
public class PARSAdapter implements IMaintenanceInfo {
private PARSSdk parsSdk;
public PARSAdapter() {}
public PARSAdapter(PARSSdk parsSdk) {
this.parsSdk = parsSdk;
}
@Override
public String getMaintenanceInfo() {
parsSdk.printMainInfo();
return null;
}
}
// B公司获取保全的适配器
public class PiccAdapter implements IMaintenanceInfo {
private PiccSdk piccSdk;
public PiccAdapter() {}
public PiccAdapter(PiccSdk piccSdk) {
this.piccSdk = piccSdk;
}
@Override
public String getMaintenanceInfo() {
piccSdk.getDataInfo();
return null;
}
}
// 5.测试和运行结果
@SpringBootTest
public class AdapterDemo2Test {
@Resource
private IDataCleaning dataCleaning;
@Resource
private IMaintenanceInfo maintenanceInfo;
@Resource
private PiccAdapter piccAdapter;
@Resource
private PARSAdapter parsAdapter;
@Test
public void test() {
dataCleaning.cleaning(maintenanceInfo);// 测试类
dataCleaning.cleaning(parsAdapter);// A公司适配
dataCleaning.cleaning(piccAdapter);// B公司适配
}
}
// 打印
获取保全信息成功
PICC 获取保全信息
平安人寿 保全信息
其他保司的接入直接增加相应的适配器,符合开闭原则。
看到这里,再去思考,如果让你定义一套日志接口的规范,其他日志框架该如何遵循你的规范呢?
定义了Logger
接口,编写log4j12适配器、jdk14适配器,哎~这就是SLF4J(Simple Logging Facade for Java)。看到facade了没,下回聊一下门面模式🥱
思考
有没有觉得适配器模式中的一些操作和另一个设计模式很像,对,代理模式。
代理模式:代理对象通过实现与被代理对象相同的接口,并持有被代理对象的引用,“真正做事”的是被代理对象。
适配器模式:适配器通过实现目标接口,并持有待适配对象的引用,“真正做事”的是待适配对象。
反正最初我是感觉两者的“作案”手法一致。区分它们,个人感觉需要从两个模式的目的和关注点去考虑:
目的:适配器是为了使接口兼容;代理则是控制被代理者的访问(使用者感知不到被代理者的存在)
关注点:适配器关注的是不同规范下的接口兼容和适配;代理模式则关注对被代理者添加额外的操作逻辑。