22、设计模式:适配器模式:接口不兼容怎么办,类适配器与对象适配器,实战案例:新旧系统对接

说实话,做开发最怕什么?最怕接手老系统。

我这些年接过不少烂摊子。有一次,客户要求把一个十年前用VB写的订单系统,对接上我们新开发的微服务。老系统输出的是XML格式,新系统只认JSON。你想想看,这俩接口完全不搭边,硬怼肯定不行。

这时候,适配器模式就派上用场了。说白了,它就是给两个不兼容的接口之间,加一个“翻译官”。

什么是适配器模式?

适配器模式的核心思想很简单:把一个类的接口,转换成客户期望的另一个接口。让原本因为接口不匹配而无法一起工作的类,能够协同工作。

我习惯把它比作电源转换插头。你从国外带回来一个电器,插头是两脚圆头的,但国内的插座是三脚扁孔的。怎么办?加一个转换插头。这个转换插头就是适配器。

适配器模式三要素:

  • 目标接口(Target): 客户期望的接口。比如新系统要求的JSON接口。
  • 被适配者(Adaptee): 需要被适配的现有类。比如老系统的XML输出类。
  • 适配器(Adapter): 核心角色。负责把被适配者的接口,转换成目标接口。

两种实现方式:类适配器 vs 对象适配器

适配器模式有两种主流实现方式。我个人更常用对象适配器,但类适配器在某些场景下也有它的优势。咱们一个一个看。

1. 类适配器(使用继承)

类适配器通过多重继承来实现。适配器同时继承目标接口和被适配者。

嗯,这里要注意:Python是支持多重继承的,所以类适配器在Python里写起来很自然。但Java、C#这些单继承语言,用起来就有点受限。

# 老系统:输出XML
class OldOrderSystem:
    def get_order_xml(self):
        return "<order><id>1001</id></order>"

# 目标接口:新系统期望的JSON格式
class NewOrderInterface:
    def get_order_json(self):
        raise NotImplementedError

# 类适配器:继承老系统,实现新接口
class OrderAdapter(OldOrderSystem, NewOrderInterface):
    def get_order_json(self):
        # 调用老系统的方法
        xml_data = self.get_order_xml()
        # 做转换(这里简化处理)
        json_data = '{"id": "1001"}'
        return json_data

# 使用
adapter = OrderAdapter()
print(adapter.get_order_json())  # 输出:{"id": "1001"}

我的经验:类适配器适合被适配者方法不多、且你不需要动态替换被适配者的场景。我曾经在一个嵌入式项目里用过类适配器,因为那个老系统的类非常稳定,几乎不改动。

2. 对象适配器(使用组合)

对象适配器通过持有被适配者的实例来实现。适配器实现目标接口,内部调用被适配者的方法。

这种方式更灵活,也是我推荐的首选方案。为什么?因为组合优于继承,这是面向对象设计的一个基本原则。

# 老系统:输出XML
class OldOrderSystem:
    def get_order_xml(self):
        return "<order><id>1001</id></order>"

# 目标接口
class NewOrderInterface:
    def get_order_json(self):
        raise NotImplementedError

# 对象适配器:持有老系统实例
class OrderAdapter(NewOrderInterface):
    def __init__(self, old_system):
        self.old_system = old_system  # 组合

    def get_order_json(self):
        xml_data = self.old_system.get_order_xml()
        # 转换逻辑
        json_data = '{"id": "1001"}'
        return json_data

# 使用
old = OldOrderSystem()
adapter = OrderAdapter(old)
print(adapter.get_order_json())

对象适配器 vs 类适配器:

对比项 类适配器 对象适配器
实现方式 继承 组合
灵活性 低(静态绑定) 高(可动态替换被适配者)
适用语言 支持多重继承的语言 所有面向对象语言
代码侵入性 高(需要继承被适配者) 低(只需持有引用)

实战案例:新旧系统对接

咱们来看一个真实的场景。我之前帮一家物流公司做系统升级,他们有一个老旧的库存管理系统,只提供CSV格式的数据导出。新系统需要RESTful API,返回JSON格式。

直接改老系统?不可能。代码都没人看得懂了。那就写个适配器吧。

# 老系统:库存数据导出为CSV
class LegacyInventory:
    def export_csv(self):
        return "SKU,Name,Quantity\nA001,Widget,100\nA002,Gadget,50"

# 新系统期望的接口
class ModernInventoryAPI:
    def get_inventory(self):
        raise NotImplementedError

# 适配器:把CSV转成JSON
class InventoryAdapter(ModernInventoryAPI):
    def __init__(self, legacy):
        self.legacy = legacy

    def get_inventory(self):
        csv_data = self.legacy.export_csv()
        # 解析CSV并转成JSON
        lines = csv_data.strip().split('\n')
        headers = lines[0].split(',')
        items = []
        for line in lines[1:]:
            values = line.split(',')
            item = dict(zip(headers, values))
            items.append(item)
        return items

# 使用
legacy = LegacyInventory()
adapter = InventoryAdapter(legacy)
inventory = adapter.get_inventory()
print(inventory)
# 输出:[{'SKU': 'A001', 'Name': 'Widget', 'Quantity': '100'}, {'SKU': 'A002', 'Name': 'Gadget', 'Quantity': '50'}]

我曾经踩过的坑:有一次我写适配器,直接把老系统的异常也“适配”过去了。老系统抛出的异常信息是乱码,结果新系统收到乱码后直接崩溃。后来我学乖了:适配器不仅要转换数据,还要转换异常。把老系统的异常捕获住,转换成新系统能理解的异常再抛出。

适配器模式的核心逻辑图

下面这张图,把适配器模式的整体结构画清楚了。你看,客户端只跟目标接口打交道,适配器在中间做翻译,被适配者完全不知道外界的变化。

适配器模式结构图 客户端 目标接口 适配器 被适配者 客户端只依赖目标接口 适配器实现目标接口,并持有被适配者 被适配者无需任何修改 调用 实现 持有

什么时候用适配器模式?

我总结了几条经验,你遇到这些情况时,可以考虑适配器模式:

  • 系统对接: 新旧系统、第三方系统、遗留系统之间的接口不匹配。
  • 复用现有类: 你想用一个现成的类,但它的接口不符合你的需求。
  • 统一接口: 多个类有相似功能,但接口不同,你想让它们对外表现一致。
  • 不想改老代码: 老代码太脆弱,一改就崩,那就包一层适配器。

我的建议:不要一开始就上适配器。先看看能不能直接改接口。如果改不了,或者改的成本太高,再用适配器。适配器虽然好用,但多了一层调用,会带来轻微的性能损耗。不过说实话,这点损耗在99%的场景下可以忽略不计。

总结

适配器模式,说白了就是“接口翻译官”。它让两个不兼容的接口能够协同工作,而不需要修改任何一方的代码。

我个人更推荐对象适配器,因为它基于组合,更灵活,也更符合面向对象的设计原则。类适配器虽然在某些场景下代码更简洁,但受限于继承的静态性,扩展起来比较麻烦。

记住:不要为了用模式而用模式。适配器模式解决的是“接口不兼容”这个具体问题。如果你的系统没有这个问题,就别硬套。我见过不少新手,代码里到处都是适配器,反而把系统搞复杂了。


公众号:蓝海资料掘金营,微信deep3321