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
def write_log(self, message):
print(f"[LEGACY] {message}")
Pero ahora deseas usar una nueva interfaz de logs:
Ejemplo
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
- Reutilización de código: permite reutilizar clases existentes sin modificar el código fuente
- Desacoplamiento: separa la lógica de conversión de interfaces de la lógica de negocio
- Flexibilidad: permite cambiar fácilmente entre diferentes implementaciones
- 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
"""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
"""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
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
"""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
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
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
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
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
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?
- 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.
- Necesitas integrar múltiples APIs de terceros, pero sus métodos de autenticación y formatos de retorno son diferentes
- 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