一、什么是适配器模式?
适配器模式(Adapter Pattern)是一种结构型模式,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
适配器模式用于解决:
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 替换依赖的外部系统
- 兼容老版本接口
二、适配器代码示例
适配器模式两种实现方式:
- 类适配器: 基于继承关系来实现;
- 对象适配器: 基于组合关系来实现。
ITarget表示要转化成的接口定义。Adaptee是一组不兼容ITarget接口定义的接口,Adaptor将Adaptee转化成一组符合ITarget接口定义的接口。
2.1 类适配器
Java
// 遗留日志组件(被适配者 Adaptee)
public class LegacyLogger {
public static final int LEVEL_INFO = 1;
public static final int LEVEL_WARN = 2;
public static final int LEVEL_ERROR = 3;
public void writeLog(int levelCode, String content) {
System.out.println("[LegacyLogger] level=" + levelCode + " | " + content);
}
public void writeError(int levelCode, String content, String stackTrace) {
System.out.println("[LegacyLogger] level=" + levelCode + " | " + content
+ " | stackTrace=" + stackTrace);
}
}
// 统一日志门面接口(目标接口)
public interface LogTarget {
void info(String message);
void warn(String message);
void error(String message, Throwable throwable);
}
// 类适配器(基于继承)
public class LegacyLoggerClassAdapter extends LegacyLogger implements LogTarget {
@Override
public void info(String message) {
writeLog(LEVEL_INFO, message);
}
@Override
public void warn(String message) {
writeLog(LEVEL_WARN, message);
}
@Override
public void error(String message, Throwable throwable) {
writeError(LEVEL_ERROR, message, toStackTraceString(throwable));
}
}
2.2 对象适配器
Java
// 遗留日志组件(被适配者 Adaptee)
public class LegacyLogger {
public static final int LEVEL_INFO = 1;
public static final int LEVEL_WARN = 2;
public static final int LEVEL_ERROR = 3;
public void writeLog(int levelCode, String content) {
System.out.println("[LegacyLogger] level=" + levelCode + " | " + content);
}
public void writeError(int levelCode, String content, String stackTrace) {
System.out.println("[LegacyLogger] level=" + levelCode + " | " + content
+ " | stackTrace=" + stackTrace);
}
}
// 统一日志门面接口(目标接口)
public interface LogTarget {
void info(String message);
void warn(String message);
void error(String message, Throwable throwable);
}
// 对象适配器(基于组合)
public class LegacyLoggerObjectAdapter implements LogTarget {
private final LegacyLogger legacyLogger;
public LegacyLoggerObjectAdapter(LegacyLogger legacyLogger) {
this.legacyLogger = legacyLogger;
}
@Override
public void info(String message) {
legacyLogger.writeLog(LegacyLogger.LEVEL_INFO, message);
}
@Override
public void warn(String message) {
legacyLogger.writeLog(LegacyLogger.LEVEL_WARN, message);
}
@Override
public void error(String message, Throwable throwable) {
legacyLogger.writeError(LegacyLogger.LEVEL_ERROR, message, toStackTraceString(throwable));
}
}
三、适配器实现方式选择
- 如果Adaptee接口并不多,那两种实现方式都可以。
- 如果Adaptee接口很多,而且Adaptee和ITarget接口定义大部分都相同,推荐使用类适配器,因为Adaptor复用父类Adaptee的接口,代码量要少一些。
- 如果Adaptee接口很多,而且Adaptee和ITarget接口定义大部分都不相同,推荐使用对象适配器,因为组合结构相对于继承更加灵活。
四、对象透传治理
理想情况下,防腐层(Anti-Corruption Layer, ACL)应当采用适配器模式来实现。其核心思想是:在调用外部 RPC 接口后,立即通过适配器将返回的外部对象转换为本服务内部的领域对象,从而完成对外部模型的隔离与防腐,避免了外部数据结构、语义或变更对本服务造成污染与耦合。
然而,理想很丰满,现实很骨感,项目中的大量老代码(俗称"屎山代码")可能并未遵循防腐层的设计原则,而是直接使用外部 RPC 接口返回的对象,并让其在本服务内部全链路流转。作为肩负治理职责的"铲屎官",我们不得不直面这类代码。对此,可以采用"同名类覆盖"的方式进行治理,具体步骤如下:
- 在本项目中构建与外部返回对象全类名完全一致的同名类,即包路径与类名均保持完全相同;
- 在该同名类中仅保留本服务实际所需的字段,剔除冗余与无关属性,以实现对外部模型的精简与收敛;
- 在线下环境进行编译部署,借助 Java 类加载机制------本项目 classpath 下的同名类会优先于外部依赖中的类被加载------来验证该同名类能否正确生效、项目能否正常运行;
- 验证无误后,对该同名类进行位置迁移:不再保持与外部对象相同的包路径,而是将其移动至防腐层所对应的正确包位置下;
- 通过 set 方法将外部对象逐字段转换为内部对象,实现代码结构的规范化与内外模型的彻底解耦。