Python 适配器模式

Python 中的适配器模式(Adapter Pattern)

适配器模式是一种结构型设计模式,其核心目的是:
将一个类的接口转换成客户端期望的另一个接口,让原本由于接口不兼容而无法一起工作的类可以协同工作。

形象比喻:就像电源适配器(将欧标插头转换成美标插座),它不改变原有功能,只做接口转换。

两种形式

  1. 类适配器(通过多重继承实现)—— 在 Python 中较少用,因为 Python 支持多继承,但不推荐过度使用。
  2. 对象适配器(通过组合实现)—— Python 中最推荐的方式,更灵活、更符合“组合优于继承”原则。

典型应用场景

  • 使用第三方库,但其接口与你的代码不匹配
  • 整合遗留系统(老代码接口过时)
  • 需要统一不同数据源的接口(如不同 API 返回格式)
  • 想复用现有类,但接口不一致

Python 示例:对象适配器(推荐)

假设我们有一个老的支付系统 OldPaymentSystem,它的方法是 make_payment(amount)
现在引入一个新的第三方支付网关 NewPaymentGateway,方法是 pay(amount, currency)
我们想让客户端代码统一使用 pay(amount, currency) 接口。

# 被适配的类(Adaptee)—— 老系统
class OldPaymentSystem:
    def make_payment(self, amount):
        print(f"Old system: Processing payment of ${amount}")
        return True

# 目标接口(Target)—— 客户端期望的接口
class PaymentProcessor:
    def pay(self, amount, currency="USD"):
        raise NotImplementedError

# 适配器(Adapter)—— 通过组合将老系统适配到新接口
class OldSystemAdapter(PaymentProcessor):
    def __init__(self, old_system: OldPaymentSystem):
        self.old_system = old_system

    def pay(self, amount, currency="USD"):
        # 转换接口:忽略 currency(老系统不支持),直接调用 make_payment
        print(f"Adapter: Converting pay({amount}, {currency}) to make_payment({amount})")
        return self.old_system.make_payment(amount)

# 新的支付网关(另一个实现)
class NewPaymentGateway(PaymentProcessor):
    def pay(self, amount, currency="USD"):
        print(f"New gateway: Processing {currency} {amount} payment securely")
        return True

# 客户端代码(统一使用 PaymentProcessor 接口)
def process_payment(processor: PaymentProcessor, amount: float):
    processor.pay(amount, "USD")

# 使用示例
if __name__ == "__main__":
    # 使用新网关
    new_gateway = NewPaymentGateway()
    process_payment(new_gateway, 100.0)

    # 使用老系统(通过适配器)
    old_system = OldPaymentSystem()
    adapter = OldSystemAdapter(old_system)
    process_payment(adapter, 200.0)

输出

New gateway: Processing USD 100.0 payment securely
Adapter: Converting pay(200.0, USD) to make_payment(200.0)
Old system: Processing payment of $200.0

客户端代码完全不需要知道底层是新网关还是老系统。

类适配器示例(了解即可,不推荐)

class OldSystemClassAdapter(OldPaymentSystem, PaymentProcessor):
    def pay(self, amount, currency="USD"):
        # 直接调用继承来的 make_payment
        return self.make_payment(amount)

# 使用
adapter = OldSystemClassAdapter()
adapter.pay(150.0)

缺点:Python 多继承容易导致复杂性,且无法适配已有实例。

更实用的例子:JSON API 适配器

假设有两个 API 返回不同格式的数据,我们想统一成 get_user_info() 返回标准字典。

import json

# 旧 API 返回 XML 字符串
class LegacyUserAPI:
    def fetch_user(self, user_id):
        # 模拟返回 XML
        return f"<user><id>{user_id}</id><name>John Doe</name><email>john@example.com</email></user>"

# 新 API 返回 JSON
class ModernUserAPI:
    def get_user(self, user_id):
        return {"id": user_id, "name": "Jane Doe", "email": "jane@example.com"}

# 目标接口
class UserService:
    def get_user_info(self, user_id):
        raise NotImplementedError

# 适配器:将旧 API 转为标准接口
class LegacyAPIAdapter(UserService):
    def __init__(self, legacy_api: LegacyUserAPI):
        self.legacy_api = legacy_api

    def get_user_info(self, user_id):
        xml_data = self.legacy_api.fetch_user(user_id)
        # 简单解析 XML(实际可用 xml.etree.ElementTree)
        import re
        data = {
            "id": re.search(r"<id>(.*?)</id>", xml_data).group(1),
            "name": re.search(r"<name>(.*?)</name>", xml_data).group(1),
            "email": re.search(r"<email>(.*?)</email>", xml_data).group(1),
        }
        return data

# 客户端统一调用
def display_user(service: UserService, user_id):
    info = service.get_user_info(user_id)
    print(f"User: {info['name']} ({info['email']})")

# 测试
legacy_adapter = LegacyAPIAdapter(LegacyUserAPI())
modern_service = ModernUserAPI()

display_user(legacy_adapter, 123)  # 通过适配器使用旧 API
# display_user(modern_service, 456)  # 如果也适配成 UserService

适配器模式结构总结

角色说明
Target客户端期望的接口(PaymentProcessor)
Client使用 Target 接口的代码
Adaptee需要被适配的原有类(OldPaymentSystem)
Adapter实现 Target,内部持有 Adaptee

优点

  • 解耦客户端与具体实现
  • 复用遗留代码
  • 符合开闭原则(扩展新适配器不修改原有代码)

缺点

  • 引入额外类,略微增加复杂度
  • 如果适配逻辑复杂,可能影响性能

Python 中的实用建议

  • 优先使用对象适配器(组合)
  • 在处理第三方库时非常常见(如不同日志库、缓存客户端、消息队列等)
  • 结合 abc 模块定义抽象目标接口更清晰
  • 很多时候可以用简单函数或装饰器实现轻量适配,不必每次都建类

如果你想看更多实际案例(如适配不同数据库客户端、日志系统、缓存 Redis vs Memcached),或者与其他模式结合使用,欢迎继续问!

文章已创建 3511

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部