Patrón Adapter
Parte de la sección Object Oriented Programming del Journey de Python de Coddy — lección 50 de 64.
El Patrón Adapter permite que objetos con interfaces incompatibles trabajen juntos. Actúa como un puente al envolver una clase existente con una nueva interfaz que los clientes esperan.
Aquí hay dos sistemas con interfaces incompatibles:
class OldPrinter:
def old_print(self, text):
return f"OLD: {text}"
class NewPrinter:
def print(self, text):
return f"NEW: {text}"La impresora antigua utiliza old_print() mientras que la nueva utiliza print().
Crea un adaptador para que la impresora antigua funcione con la nueva interfaz:
class PrinterAdapter:
def __init__(self, old_printer):
self.old_printer = old_printer
def print(self, text):
# Adaptar la interfaz antigua a la nueva interfaz
return self.old_printer.old_print(text)El adaptador envuelve la impresora antigua y proporciona la interfaz esperada.
Usa ambas impresoras con el mismo código de cliente:
def print_document(printer, text):
return printer.print(text) # Espera el método print()
# Usa la nueva impresora directamente
new_printer = NewPrinter()
print(print_document(new_printer, "Hello"))
# Usa la impresora antigua a través del adaptador
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)
print(print_document(adapter, "Hello"))Crea otro ejemplo con reproductores multimedia:
class Mp3Player:
def play_mp3(self, filename):
return f"Playing MP3: {filename}"
class Mp4Player:
def play_mp4(self, filename):
return f"Playing MP4: {filename}"
class MediaAdapter:
def __init__(self, player, audio_type):
self.player = player
self.audio_type = audio_type
def play(self, filename):
if self.audio_type == "mp3":
return self.player.play_mp3(filename)
elif self.audio_type == "mp4":
return self.player.play_mp4(filename)class AudioPlayer:
def play(self, audio_type, filename):
if audio_type == "mp3":
return Mp3Player().play_mp3(filename)
else:
adapter = MediaAdapter(Mp4Player(), audio_type)
return adapter.play(filename)
player = AudioPlayer()
print(player.play("mp3", "song.mp3"))
print(player.play("mp4", "video.mp4"))Salida:
NEW: Hello
OLD: Hello
Playing MP3: song.mp3
Playing MP4: video.mp4Punto clave: El Patrón Adapter permite que interfaces incompatibles trabajen juntas envolviendo una clase existente con una nueva interfaz. El adaptador traduce las llamadas de la interfaz esperada a la interfaz real del objeto envuelto. Esto es útil para integrar código heredado o bibliotecas de terceros sin modificar el código existente.
Desafío
IntermedioEn este desafío, implementarás el patrón de diseño Adapter para integrar sistemas heredados con una arquitectura de aplicación moderna. El patrón Adapter permite que objetos con interfaces incompatibles trabajen juntos mediante la creación de un envoltorio (adaptador) que traduce una interfaz a otra.
Tu empresa tiene un sistema heredado de análisis y visualización de datos que necesita ser integrado con una nueva plataforma de analítica moderna. Los componentes heredados tienen interfaces que son incompatibles con el nuevo sistema. En lugar de reescribir el código heredado (lo cual sería arriesgado y costoso), se te ha encomendado la tarea de crear adaptadores para que estos componentes funcionen con el nuevo sistema.
Necesitas:
- Comprender las interfaces modernas definidas en
interfaces.py(este archivo no se puede modificar) - Examinar los componentes heredados en
legacy_system.py(este archivo no se puede modificar) - Implementar clases de adaptador en
adapters.pyque hagan que los componentes heredados sean compatibles con las interfaces modernas - Crear un sistema moderno en
modern_system.pyque utilice estas interfaces
Hoja de referencia
El patrón Adapter permite que objetos con interfaces incompatibles trabajen juntos envolviendo una clase existente con una nueva interfaz que los clientes esperan.
Estructura básica del adaptador:
class PrinterAdapter:
def __init__(self, old_printer):
self.old_printer = old_printer
def print(self, text):
# Adaptar la interfaz antigua a la nueva interfaz
return self.old_printer.old_print(text)Ejemplo con interfaces de impresora incompatibles:
class OldPrinter:
def old_print(self, text):
return f"OLD: {text}"
class NewPrinter:
def print(self, text):
return f"NEW: {text}"
# El código del cliente espera el método print()
def print_document(printer, text):
return printer.print(text)
# Usar la impresora antigua a través del adaptador
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)
print(print_document(adapter, "Hello")) # OLD: HelloEjemplo de adaptador de reproductor de medios:
class MediaAdapter:
def __init__(self, player, audio_type):
self.player = player
self.audio_type = audio_type
def play(self, filename):
if self.audio_type == "mp3":
return self.player.play_mp3(filename)
elif self.audio_type == "mp4":
return self.player.play_mp4(filename)El adaptador traduce las llamadas de la interfaz esperada a la interfaz real del objeto envuelto, permitiendo la integración de código heredado sin modificaciones.
Pruébalo tú mismo
# Importar todas las clases necesarias
from adapters import LegacyDataAnalyzerAdapter, LegacyChartGeneratorAdapter
from modern_system import AnalyticsSystem
# Manejador integral de casos de prueba
test_case = input()
if test_case == "basic_adapter_test":
# Probar la funcionalidad básica del adaptador
processor = LegacyDataAnalyzerAdapter()
data = [1, 2, 3, 4, 5]
processor.process_data(data)
results = processor.get_results()
print(f"Results: {results}")
elif test_case == "visualizer_adapter_test":
# Probar el adaptador del visualizador
visualizer = LegacyChartGeneratorAdapter("bar")
data = [10, 20, 30, 40, 50]
success = visualizer.visualize(data)
if success:
export_success = visualizer.export_visualization("test_chart.png")
print(f"Visualization exported: {export_success}")
elif test_case == "analytics_system_test":
# Probar el sistema de análisis completo
processor = LegacyDataAnalyzerAdapter()
visualizer = LegacyChartGeneratorAdapter("line")
system = AnalyticsSystem(processor, visualizer)
data = [15, 25, 35, 45, 55]
result = system.analyze_and_visualize(data, "analytics_output.png")
print(f"System result: {result}")
elif test_case == "validation_error_test":
# Probar errores de validación
processor = LegacyDataAnalyzerAdapter()
try:
processor.process_data("invalid_data")
print("Validation failed - should have raised ValueError")
except ValueError as e:
print(f"Validation error caught: {e}")
elif test_case == "empty_data_test":
# Probar con datos vacíos
processor = LegacyDataAnalyzerAdapter()
try:
processor.process_data([])
results = processor.get_results()
print(f"Empty data results: {results}")
except Exception as e:
print(f"Empty data error: {e}")
elif test_case == "chart_types_test":
# Probar diferentes tipos de gráficos
chart_types = ["bar", "line", "pie"]
data = [5, 15, 25, 35]
for chart_type in chart_types:
visualizer = LegacyChartGeneratorAdapter(chart_type)
success = visualizer.visualize(data)
print(f"{chart_type} chart created: {success}")
elif test_case == "file_extension_test":
# Probar el manejo de extensiones de archivo
visualizer = LegacyChartGeneratorAdapter()
data = [1, 2, 3, 4]
visualizer.visualize(data)
# Probar varios formatos de nombre de archivo
filenames = ["test", "test.jpg", "test.pdf", "test.png"]
for filename in filenames:
success = visualizer.export_visualization(filename)
print(f"Export '{filename}': {success}")
elif test_case == "summary_test":
# Probar el resumen del análisis
processor = LegacyDataAnalyzerAdapter()
visualizer = LegacyChartGeneratorAdapter()
system = AnalyticsSystem(processor, visualizer)
data = [10, 20, 30, 40, 50, 60]
system.analyze_and_visualize(data)
summary = system.get_analysis_summary()
print("Analysis Summary:")
print(summary)
elif test_case == "mixed_data_test":
# Probar con datos numéricos mixtos
processor = LegacyDataAnalyzerAdapter()
data = [1.5, 2, 3.7, 4, 5.2]
processor.process_data(data)
results = processor.get_results()
print(f"Mixed data results: {results}")
elif test_case == "invalid_numeric_test":
# Probar con datos numéricos inválidos
processor = LegacyDataAnalyzerAdapter()
try:
processor.process_data([1, 2, "three", 4, 5])
print("Validation failed - should have raised ValueError")
except ValueError as e:
print(f"Invalid numeric data error: {e}")
elif test_case == "no_results_summary_test":
# Probar resumen sin resultados
processor = LegacyDataAnalyzerAdapter()
visualizer = LegacyChartGeneratorAdapter()
system = AnalyticsSystem(processor, visualizer)
summary = system.get_analysis_summary()
print(f"No results summary: {summary}")
elif test_case == "large_dataset_test":
# Probar con un conjunto de datos más grande
processor = LegacyDataAnalyzerAdapter()
data = list(range(1, 101)) # 1 a 100
processor.process_data(data)
results = processor.get_results()
print(f"Large dataset - Mean: {results.get('mean', 'N/A'):.2f}")
print(f"Large dataset - Min: {results.get('min', 'N/A')}")
print(f"Large dataset - Max: {results.get('max', 'N/A')}")
elif test_case == "complete_workflow_test":
# Probar el flujo de trabajo completo con múltiples operaciones
processor = LegacyDataAnalyzerAdapter()
bar_visualizer = LegacyChartGeneratorAdapter("bar")
line_visualizer = LegacyChartGeneratorAdapter("line")
data = [12, 18, 25, 32, 28, 35, 42]
# Crear sistemas de análisis con diferentes visualizadores
bar_system = AnalyticsSystem(processor, bar_visualizer)
line_system = AnalyticsSystem(LegacyDataAnalyzerAdapter(), line_visualizer)
# Ejecutar análisis
bar_result = bar_system.analyze_and_visualize(data, "bar_chart.png")
line_result = line_system.analyze_and_visualize(data, "line_chart.png")
print("Complete workflow test completed")
print(f"Bar chart result: {bar_result['visualization_file']}")
print(f"Line chart result: {line_result['visualization_file']}")
# Obtener resúmenes
print("Bar system summary:")
print(bar_system.get_analysis_summary())
print("Line system summary:")
print(line_system.get_analysis_summary())Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Fundamentos de la POO
Archivos externosIntroducción a la POOClases vs ObjetosEl parámetro selfMétodosAtributosMétodo constructor (__init__)Resumen - Calculadora simple4Herencia
Herencia básicaLa función super()Sobrescritura de métodosHerencia múltipleOrden de resolución de métodosResumen - Jerarquía de empleados7Métodos especiales
Introducción a los métodos mágicosSobrecarga de operadoresMétodos mágicos de contenedoresRepaso - Lista personalizada10Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy2Decoradores
Introducción a los decoradoresDecorador propertyDecorador de métodos estáticosDecorador de métodos de clase5Polimorfismo
Sobreescritura de métodos revisitadaDuck TypingClases y métodos abstractosDiseño de interfacesResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaMixinsMétodos estáticos y de claseDecoradores de claseGestores de contexto11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Propiedades de clase
Variables de instancia vs de claseDecoradores de propiedadesAtributos privadosResumen - Gestor de cuentas bancarias