Patrón Adaptador en Python

El patrón adaptador es un patrón de diseño estructural que permite que interfaces incompatibles colaboren entre sí. Así como un adaptador de corriente del mundo real permite que los enchufes de electrodomésticos de diferentes países se conecten a un tomacorriente local, en programación el patrón adaptador actúa como un puente entre dos interfaces incompatibles.

Conceptos clave

El patrón adaptador resuelve principalmente el problema de la falta de coincidencia de interfaces. Cuando necesitamos usar una clase existente pero su interfaz no satisface nuestros requisitos, el patrón adaptador es útil. Crea una capa intermedia (el adaptador) para convertir la interfaz, de modo que las clases originalmente incompatibles puedan trabajar juntas.

Estructura del patrón

El patrón adaptador incluye tres roles principales:

  • Interfaz objetivo (Target): la interfaz que espera el cliente
  • Adaptador (Adapter): implementa la interfaz objetivo y envuelve al adaptado
  • Adaptado (Adaptee): la clase existente que necesita ser adaptada

¿Por qué se necesita el patrón adaptador?

Escenario de problema real

Imagina que tienes un sistema de registro de logs existente que utiliza un método específico para registrar logs:

Ejemplo

class LegacyLogger:
    def write_log(self, message):
        print(f"[LEGACY] {message}")

Pero ahora deseas usar una nueva interfaz de logs:

Ejemplo

class NewLogger:
    def log(self, level, message):
        print(f"[{level.upper()}] {message}")

Sin un adaptador, tendrías que modificar todo el código que llama al sistema de logs antiguo, lo que requiere tiempo y es propenso a errores.

Ventajas del adaptador

  1. Reutilización de código: permite reutilizar clases existentes sin modificar el código fuente
  2. Desacoplamiento: separa la lógica de conversión de interfaces de la lógica de negocio
  3. Flexibilidad: permite cambiar fácilmente entre diferentes implementaciones
  4. Cumple el principio abierto/cerrado: abierto a la extensión, cerrado a la modificación

Métodos de implementación del patrón adaptador

Adaptador de clase (usando herencia)

El adaptador de clase implementa la adaptación de interfaces mediante herencia múltiple:

Ejemplo

class LegacyLogger:
    """Clase de log antigua adaptada"""
    def write_log(self, message):
        print(f"[LEGACY] {message}")

class NewLoggerInterface:
    """Interfaz objetivo"""
    def log(self, level, message):
        pass

class LoggerAdapter(NewLoggerInterface, LegacyLogger):
    """Adaptador de clase: implementado mediante herencia"""
    def log(self, level, message):
        # Convertir la nueva interfaz en la antigua
        formatted_message = f"{level}: {message}"
        self.write_log(formatted_message)

# Ejemplo de uso
logger = LoggerAdapter()
logger.log("INFO", "Sistema iniciado con éxito")
logger.log("ERROR", "Error de conexión a la base de datos")

Salida:

[LEGACY] INFO: 系统启动成功
[LEGACY] ERROR: 数据库连接失败

Adaptador de objeto (usando composición)

El adaptador de objeto se implementa mediante composición, que es la forma más común:

Ejemplo

class LegacyPayment:
    """Sistema de pago antiguo adaptado"""
    def process_payment(self, amount_in_dollars):
        print(f"Procesando pago: ${amount_in_dollars}")
        return True

class NewPaymentInterface:
    """Nueva interfaz de pago"""
    def pay(self, amount, currency="CNY"):
        pass

class PaymentAdapter(NewPaymentInterface):
    """Adaptador de objeto - implementado mediante composición"""
   
    def __init__(self, legacy_payment):
        self.legacy_payment = legacy_payment
   
    def pay(self, amount, currency="CNY"):
        # Lógica de conversión de moneda
        if currency == "CNY":
            amount_in_dollars = amount / 7.0  # Conversión simplificada de tipo de cambio
        else:
            amount_in_dollars = amount
       
        # Llamar a la interfaz del sistema antiguo
        return self.legacy_payment.process_payment(amount_in_dollars)

# Ejemplo de uso
legacy_payment = LegacyPayment()
payment = PaymentAdapter(legacy_payment)

# Usar la nueva interfaz para llamar al sistema antiguo
payment.pay(700, "CNY")  # Pagar 700 yuanes RMB
payment.pay(100, "USD")  # Pagar 100 dólares

Salida:

处理支付: $100.0
处理支付: $100

Casos de aplicación real

Caso 1: Adaptador de formato de datos

Ejemplo

import json

class XMLData:
    """Fuente de datos XML"""
    def get_xml_data(self):
        return """
        <users>
            <user>
<name>Zhang San</name>
                <age>25</age>
            </user>
            <user>
<name>Li Si</name>
                <age>30</age>
            </user>
        </users>
        """


class JSONProcessor:
    """Procesador JSON (espera formato JSON)"""
    def process_json(self, json_data):
        data = json.loads(json_data)
        for user in data['users']:
            print(f"Nombre: {user['name']}, Edad: {user['age']}")

class XMLToJSONAdapter:
    """Adaptador de XML a JSON"""
   
    def __init__(self, xml_data):
        self.xml_data = xml_data
   
    def convert_to_json(self):
        # Conversión simplificada de XML a JSON
        xml_content = self.xml_data.get_xml_data()
        # En aplicaciones reales, aquí se debería usar xml.etree.ElementTree y otras bibliotecas para el análisis
        json_data = {
            "users": [
                {"name": "Zhang San", "age": 25},
                {"name": "Li Si", "age": 30}
            ]
        }
        return json.dumps(json_data, ensure_ascii=False)

# Usar el adaptador
xml_source = XMLData()
adapter = XMLToJSONAdapter(xml_source)
json_processor = JSONProcessor()

json_data = adapter.convert_to_json()
json_processor.process_json(json_data)

Salida:

姓名: 张三, 年龄: 25
姓名: 李四, 年龄: 30

Caso 2: Adaptador de servicios de terceros

Ejemplo

class WeChatPay:
    """SDK de WeChat Pay"""
    def wechat_pay(self, amount, user_id):
        print(f"WeChat Pay: el usuario {user_id} paga {amount} yuanes")
        return {"status": "success", "transaction_id": "wx_123456"}

class AliPay:
    """SDK de Alipay"""
    def alipay(self, money, user_account):
        print(fAlipay: la cuenta {user_account} paga {money} yuanes)
        return {"code": 200, "trade_no": "ali_789012"}

class UnifiedPaymentAdapter:
    Adaptador de pago unificado
   
    def __init__(self, payment_service):
        self.payment_service = payment_service
   
    def pay(self, amount, user_info):
        # Interfaz de pago unificada
        if isinstance(self.payment_service, WeChatPay):
            return self.payment_service.wechat_pay(amount, user_info)
        elif isinstance(self.payment_service, AliPay):
            return self.payment_service.alipay(amount, user_info)
        else:
            raise ValueError(Método de pago no soportado)

# El código del cliente puede realizar llamadas unificadas
def process_order(payment_adapter, amount, user_info):
    Procesar pago de pedidos
    result = payment_adapter.pay(amount, user_info)
    print(fResultado del pago: {result})
    return result

# Ejemplo de uso
wechat_pay = WeChatPay()
ali_pay = AliPay()

wechat_adapter = UnifiedPaymentAdapter(wechat_pay)
ali_adapter = UnifiedPaymentAdapter(ali_pay)

# Método de llamada unificado
process_order(wechat_adapter, 100, "user_001")
process_order(ali_adapter, 200, "user_002")

Salida:

微信支付: 用户user_001支付100元
支付结果: {'status': 'success', 'transaction_id': 'wx_123456'}
支付宝: 账户user_002支付200元
支付结果: {'code': 200, 'trade_no': 'ali_789012'}

Mejores prácticas del patrón adaptador

1. Elija el método de implementación adecuado

Método de implementación Escenario aplicable Ventajas Desventajas
Adaptador de clase Pocas clases adaptadas y la interfaz objetivo es simple Implementación simple y menos código Requiere herencia múltiple, puede hacer compleja la jerarquía de clases
Adaptador de objeto La mayoría de los escenarios, especialmente cuando se necesita adaptar varias clases Más flexible, cumple el principio de composición frente a herencia Requiere crear un objeto adaptador

2. Mantenga la responsabilidad única del adaptador

Ejemplo

# Buena práctica: responsabilidad única
class DataFormatAdapter:
    Solo responsable de la conversión del formato de datos
    def convert_format(self, data):
        pass

# Mala práctica: demasiadas responsabilidades
class BadAdapter:
    Maneja simultáneamente conversión de formato, validación, caché, etc.
    def convert_format(self, data):
        pass
   
    def validate_data(self, data):
        pass
   
    def cache_data(self, data):
        pass

3. Use abstracción de interfaces

Ejemplo

from abc import ABC, abstractmethod

class PaymentInterface(ABC):
    Abstracción de interfaz de pago
    @abstractmethod
    def pay(self, amount, user_info):
        pass

class LoggerInterface(ABC):
    Abstracción de interfaz de registro
    @abstractmethod
    def log(self, level, message):
        pass

Problemas comunes y soluciones

Problema 1: Demasiados adaptadores aumentan la complejidad

Solución: use el patrón de fábrica para crear adaptadores

Ejemplo

class AdapterFactory:
    Fábrica de adaptadores
   
    @staticmethod
    def create_payment_adapter(service_type):
        if service_type == "wechat":
            return UnifiedPaymentAdapter(WeChatPay())
        elif service_type == "alipay":
            return UnifiedPaymentAdapter(AliPay())
        else:
            raise ValueError(fTipo de pago no soportado: {service_type})

# Usar la fábrica para crear adaptadores
wechat_adapter = AdapterFactory.create_payment_adapter("wechat")
ali_adapter = AdapterFactory.create_payment_adapter("alipay")

Problema 2: Consideraciones de rendimiento

Solución: implemente carga diferida y almacenamiento en caché

Ejemplo

class CachingAdapter:
    Adaptador con caché
   
    def __init__(self, adaptee):
        self.adaptee = adaptee
        self._cache = {}
   
    def process_data(self, key):
        if key not in self._cache:
            # Simular operación que consume tiempo
            result = self.adaptee.expensive_operation(key)
            self._cache[key] = result
        return self._cache[key]

Ejercicios prácticos

Ejercicio 1: Crear un adaptador de formato de archivo

Implementa un adaptador de formato de archivo que convierta datos en formato CSV a formato JSON:

Ejemplo

class CSVReader:
    Lector de archivos CSV
    def read_csv(self, filepath):
        # Simular lectura de archivo CSV
        return [
            ["name", "age", "city"],
            ["Alice", "25", "Beijing"],
            ["Bob", "30", "Shanghai"]
        ]

class JSONExporter:
    Exportador JSON
    def export_json(self, data):
        import json
        print(Exportar datos JSON:)
        print(json.dumps(data, indent=2, ensure_ascii=False))

# Implementa aquí la clase CSVToJSONAdapter
# Tu código...

# Código de prueba
csv_reader = CSVReader()
json_exporter = JSONExporter()

# Crear adaptador y probar
adapter = CSVToJSONAdapter(csv_reader)
json_data = adapter.convert_to_json()
json_exporter.export_json(json_data)

Ejercicio 2: Pensamiento de diseño

Considera el siguiente escenario: ¿cómo usarías el patrón adaptador?

  1. Tu aplicación necesita soportar múltiples bases de datos (MySQL, PostgreSQL, SQLite), pero cada base de datos tiene una forma de conexión diferente.
  2. Necesitas integrar múltiples APIs de terceros, pero sus métodos de autenticación y formatos de retorno son diferentes
  3. Tu sistema necesita soportar múltiples colas de mensajes (RabbitMQ, Kafka, Redis)

Diseña una solución del patrón adaptador para uno de estos escenarios.


Resumen

El patrón adaptador es una herramienta eficaz para resolver problemas de incompatibilidad de interfaces. A través de este artículo, deberías dominar:

  • Los conceptos básicos y los escenarios de aplicación del patrón adaptador
  • Las formas de implementación del adaptador de clase y del adaptador de objeto
  • Técnicas de aplicación en proyectos reales
  • Mejores prácticas y consideraciones del patrón adaptador

Recuerda, la idea central del patrón adaptador es "convertir" en lugar de "modificar". Nos ayuda a integrar nuevas funcionalidades sin cambiar el código existente, y es un patrón de diseño importante al mantener sistemas grandes.

En el desarrollo real, el uso razonable del patrón adaptador puede hacer que tu código sea más flexible, mantenible y fácil de extender.

Otras extensiones