设计模式-适配器模式

81 阅读5分钟

写在前面

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<StringLogisticsService> 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);
    }
}
设计好处
  1. 透明转换:使用者无需关心字节到字符的转换细节
  2. 装饰增强:可配合BufferedReader等装饰器叠加功能
  3. 统一接口:所有字符输入源都通过Reader接口操作

长话短说

核心思想

🛡 接口盾牌:在系统核心逻辑与外部组件之间建立防护层
🔄 转换中枢:将异构系统的交互转化为标准对话机制
🎭 多态化身:让不同对象呈现统一的行为特征

最佳实践场景

  1. 系统升级过渡期:新旧系统共存时作为兼容层
  2. 跨平台集成:对接不同技术栈实现的子系统
  3. 第三方SDK整合:统一不同厂商的API调用方式

实施步骤指南

  1. 识别冲突点:找到接口不匹配的具体表现
  2. 定义标准接口:确定客户端期望的统一形式
  3. 构建适配器:实现接口转换的核心逻辑
  4. 配置注入:通过依赖注入管理适配器实例
  5. 验证隔离:确保适配器错误不会影响核心系统

何时选择适配器模式?

  • 当需要整合多个接口不同的相似功能组件时
  • 系统需要保持稳定,但外部依赖可能频繁变化时
  • 希望避免第三方库的实现细节污染业务代码时

何时避免使用?

  • 接口差异过大,转换成本高于重写成本
  • 需要深度定制被适配对象的行为
  • 性能敏感场景(适配器可能引入额外开销)