写在前面
❝
Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!
第一话:异常问题现象
背景
某电商平台需要接入第三方物流公司的API来实现订单发货功能。平台原系统基于XmlLogisticsService开发,该服务仅支持XML格式的请求参数。然而,新合作的物流公司JsonLogisticsService仅提供JSON格式的API接口。
问题现象
// 原系统代码(仅支持XML)
public class XmlLogisticsService {
public String sendXmlRequest(String xmlData) {
return "原物流运输数据: " + xmlData;
}
}
// 新物流服务(仅支持JSON)
public class JsonLogisticsService {
public String sendJsonRequest(String jsonData) {
return "新物流运输数据: " + jsonData;
}
}
调用新物流服务时,系统抛出异常:
Exception: Incompatible request format (JSON required, got XML)
紧急修复代码
临时解决方案
开发团队在调用新物流服务前手动转换数据格式:
public class LogisticsClient {
private XmlLogisticsService xmlLogisticsService = new XmlLogisticsService();
private JsonLogisticsService jsonLogisticsService = new JsonLogisticsService();
public String shipOrder(String data, boolean useJsonService) {
if (useJsonService) {
// 手动转换XML到JSON
String jsonData = convertXmlToJson(data);
return jsonLogisticsService.sendJsonRequest(jsonData);
}
else {
return xmlLogisticsService.sendXmlRequest(data);
}
}
private String convertXmlToJson(String xml) {
return xml.replace("Xml", "Json");
}
}
修复结果
表面成效
- 系统暂时支持新旧物流服务。
- 订单发货功能恢复正常。
遗留问题
- 再次新增第三方服务时需要增加额外的转换,并且依然需要将转换的逻辑添加在业务逻辑中。
第二话:紧急修复的代价
新需求引发崩溃
当需要接入第三家物流商ProtocolLogisticsService(需要加密协议处理,并使用Protocol Buffers格式),缺陷问题被放大。
// 新接入的物流服务
public class ProtocolLogisticsService {
public String sendProtocolRequest(byte[] protoBuffData) {
return "4399协议加密数据: >" + new String(protoBuffData);
}
}
// 客户端调用出现混乱
public class LogisticsClient {
private XmlLogisticsService xmlLogisticsService = new XmlLogisticsService();
private JsonLogisticsService jsonLogisticsService = new JsonLogisticsService();
private ProtocolLogisticsService protocolLogisticsService = new ProtocolLogisticsService();
// 新增的临时转换逻辑
public String shipOrder(String data, int serviceType) {
if (serviceType == 1) {
// 手动转换XML到JSON
String jsonData = convertXmlToJson(data);
return jsonLogisticsService.sendJsonRequest(jsonData);
}
else if (serviceType == 2) {
// 手动转换xml到byte数组
byte[] byteData = convertXmlToByte(data);
return protocolLogisticsService.sendProtocolRequest(byteData);
}
// else if .....
else {
return xmlLogisticsService.sendXmlRequest(data);
}
}
/**
* 将 XML 转化为 JSON
*/
private String convertXmlToJson(String xml) {
return xml.replace("Xml", "Json");
}
/**
* 将 XML 转化为 byte
*/
private byte[] convertXmlToByte(String xml) {
return xml.getBytes();
}
}
系统出现以下问题:
- 新增服务需修改核心业务类
- 单元测试无法覆盖每种情况
设计模式解析
适配器模式核心要素
| 要素 | 说明 |
|---|---|
| 目标接口 | 提供统一的数据解析接口 |
| 适配器 | 实现目标接口,包装被适配对象 |
| 被适配者 | 需要被整合的现有组件 |
| 客户端 | 通过目标接口与适配器交互,无需关注具体实现 |
重构:适配器模式实践
步骤1:定义目标接口
/**
* 运输通用接口
*/
public interface LogisticsService {
String ship(String data);
}
步骤2:实现原有服务适配
public class XmlLogisticsAdapter implements LogisticsService {
public final XmlLogisticsService xmlLogisticsService;
public XmlLogisticsAdapter(XmlLogisticsService xmlLogisticsService) {
this.xmlLogisticsService = xmlLogisticsService;
}
@Override
public String ship(String data) {
return xmlLogisticsService.sendXmlRequest(data);
}
}
public class JsonLogisticsAdapter implements LogisticsService {
private final JsonLogisticsService jsonLogisticsService;
public JsonLogisticsAdapter(JsonLogisticsService jsonLogisticsService) {
this.jsonLogisticsService = jsonLogisticsService;
}
@Override
public String ship(String data) {
String json = convertXmlToJson(data);
return jsonLogisticsService.sendJsonRequest(json);
}
/**
* 将 XML 转化为 JSON
*/
private String convertXmlToJson(String xml) {
return xml.replace("Xml", "Json");
}
}
步骤3:创建新服务适配器
// 新物流服务适配器
public class ProtocolLogisticsAdapter implements LogisticsService {
private final ProtocolLogisticsService protocolLogisticsService;
public ProtocolLogisticsAdapter(ProtocolLogisticsService protocolLogisticsService) {
this.protocolLogisticsService = protocolLogisticsService;
}
@Override
public String ship(String data) {
byte[] bytes = convertXmlToByte(data);
return protocolLogisticsService.sendProtocolRequest(bytes);
}
/**
* 将 XML 转化为 byte
*/
private byte[] convertXmlToByte(String xml) {
return xml.getBytes();
}
}
public class ProtocolLogisticsService {
public String sendProtocolRequest(byte[] protoBuffData) {
return "4399协议加密数据: " + new String(protoBuffData);
}
}
步骤4:客户端统一调用
public class LogisticsClient {
private Map<String, LogisticsService> services = new HashMap<>();
public LogisticsClient() {
services.put(XML_LOGISTICS.getType(), new XmlLogisticsAdapter(new XmlLogisticsService()));
services.put(JSON_LOGISTICS.getType(), new JsonLogisticsAdapter(new JsonLogisticsService()));
services.put(PROTOCOL_LOGISTICS.getType(), new ProtocolLogisticsAdapter(new ProtocolLogisticsService()));
}
private ProtocolLogisticsService protocolLogisticsService = new ProtocolLogisticsService();
public String shipOrder(String data, LogisticsTypeEnum logisticsTypeEnum) {
LogisticsService logisticsService = services.get(logisticsTypeEnum.getType());
if (logisticsService == null) {
throw new IllegalArgumentException("物流服务解析类型异常!");
}
return logisticsService.ship(data);
}
}
重构后效果
系统改进点
- 新增物流服务只需添加对应适配器
- 数据转换逻辑封装在适配器中不会影响业务逻辑
- 每个适配器可独立进行单元测试
- 客户端代码减少大量的if-else逻辑,易于维护
Java I/O 库中的应用
InputStream与Reader适配
Java I/O库使用适配器模式连接字节流与字符流:
// 目标接口:Reader(字符流)
public abstract class Reader {
public int read(char[] cbuf) throws IOException;
}
// 被适配者:InputStream(字节流)
public abstract class InputStream {
public int read(byte[] b) throws IOException;
}
// 适配器:InputStreamReader
public class InputStreamReader extends Reader {
private final StreamDecoder sd;
public InputStreamReader(InputStream in) {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null);
}
public int read(char[] cbuf) throws IOException {
return sd.read(cbuf, 0, cbuf.length);
}
}
设计好处
- 透明转换:使用者无需关心字节到字符的转换细节
- 装饰增强:可配合BufferedReader等装饰器叠加功能
- 统一接口:所有字符输入源都通过Reader接口操作
长话短说
核心思想
🛡 接口盾牌:在系统核心逻辑与外部组件之间建立防护层
🔄 转换中枢:将异构系统的交互转化为标准对话机制
🎭 多态化身:让不同对象呈现统一的行为特征
最佳实践场景
- 系统升级过渡期:新旧系统共存时作为兼容层
- 跨平台集成:对接不同技术栈实现的子系统
- 第三方SDK整合:统一不同厂商的API调用方式
实施步骤指南
- 识别冲突点:找到接口不匹配的具体表现
- 定义标准接口:确定客户端期望的统一形式
- 构建适配器:实现接口转换的核心逻辑
- 配置注入:通过依赖注入管理适配器实例
- 验证隔离:确保适配器错误不会影响核心系统
何时选择适配器模式?
- 当需要整合多个接口不同的相似功能组件时
- 系统需要保持稳定,但外部依赖可能频繁变化时
- 希望避免第三方库的实现细节污染业务代码时
何时避免使用?
- 接口差异过大,转换成本高于重写成本
- 需要深度定制被适配对象的行为
- 性能敏感场景(适配器可能引入额外开销)