Patron Adaptateur
Fait partie de la section Object Oriented Programming du Journey Python de Coddy — leçon 50 sur 64.
Le motif Adaptateur permet à des objets aux interfaces incompatibles de collaborer. Il agit comme un pont en enveloppant une classe existante avec une nouvelle interface attendue par les clients.
Voici deux systèmes avec des interfaces incompatibles :
class OldPrinter:
def old_print(self, text):
return f"OLD: {text}"
class NewPrinter:
def print(self, text):
return f"NEW: {text}"L'ancienne imprimante utilise old_print() tandis que la nouvelle utilise print().
Créez un adaptateur pour faire fonctionner l'ancienne imprimante avec la nouvelle interface :
class PrinterAdapter:
def __init__(self, old_printer):
self.old_printer = old_printer
def print(self, text):
# Adapter l'ancienne interface à la nouvelle interface
return self.old_printer.old_print(text)L'adaptateur enveloppe l'ancienne imprimante et fournit l'interface attendue.
Utilisez les deux imprimantes avec le même code client :
def print_document(printer, text):
return printer.print(text) # Attend la méthode print()
# Utiliser la nouvelle imprimante directement
new_printer = NewPrinter()
print(print_document(new_printer, "Hello"))
# Utiliser l'ancienne imprimante via l'adaptateur
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)
print(print_document(adapter, "Hello"))Créez un autre exemple avec des lecteurs multimédias :
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"))Sortie :
NEW: Hello
OLD: Hello
Playing MP3: song.mp3
Playing MP4: video.mp4Point clé : Le patron Adaptateur permet à des interfaces incompatibles de travailler ensemble en enveloppant une classe existante avec une nouvelle interface. L'adaptateur traduit les appels de l'interface attendue vers l'interface réelle de l'objet enveloppé. Ceci est utile pour intégrer du code hérité ou des bibliothèques tierces sans modifier le code existant.
Défi
MoyenDans ce défi, vous allez implémenter le patron de conception Adaptateur (Adapter) pour intégrer des systèmes hérités (legacy) à une architecture d'application moderne. Le patron Adaptateur permet à des objets ayant des interfaces incompatibles de travailler ensemble en créant une enveloppe (wrapper) qui traduit une interface en une autre.
Votre entreprise dispose d'un système hérité d'analyse et de visualisation de données qui doit être intégré à une nouvelle plateforme d'analyse moderne. Les composants hérités ont des interfaces incompatibles avec le nouveau système. Au lieu de réécrire le code hérité (ce qui serait risqué et coûteux), vous avez été chargé de créer des adaptateurs pour faire fonctionner ces composants avec le nouveau système.
Vous devez :
- Comprendre les interfaces modernes définies dans
interfaces.py(ce fichier ne peut pas être modifié) - Examiner les composants hérités dans
legacy_system.py(ce fichier ne peut pas être modifié) - Implémenter des classes d'adaptateurs dans
adapters.pyqui rendent les composants hérités compatibles avec les interfaces modernes - Créer un système moderne dans
modern_system.pyqui utilise ces interfaces
Aide-mémoire
Le patron Adaptateur (Adapter Pattern) permet à des objets ayant des interfaces incompatibles de travailler ensemble en enveloppant une classe existante avec une nouvelle interface attendue par les clients.
Structure de base de l'adaptateur :
class PrinterAdapter:
def __init__(self, old_printer):
self.old_printer = old_printer
def print(self, text):
# Adapter l'ancienne interface à la nouvelle interface
return self.old_printer.old_print(text)Exemple avec des interfaces d'imprimante incompatibles :
class OldPrinter:
def old_print(self, text):
return f"OLD: {text}"
class NewPrinter:
def print(self, text):
return f"NEW: {text}"
# Le code client attend la méthode print()
def print_document(printer, text):
return printer.print(text)
# Utiliser l'ancienne imprimante via l'adaptateur
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)
print(print_document(adapter, "Hello")) # OLD: HelloExemple d'adaptateur de lecteur multimédia :
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)L'adaptateur traduit les appels de l'interface attendue vers l'interface réelle de l'objet enveloppé, permettant l'intégration de code hérité sans modification.
Essayez vous-même
# Importer toutes les classes nécessaires
from adapters import LegacyDataAnalyzerAdapter, LegacyChartGeneratorAdapter
from modern_system import AnalyticsSystem
# Gestionnaire de cas de test complet
test_case = input()
if test_case == "basic_adapter_test":
# Tester la fonctionnalité de base de l'adaptateur
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":
# Tester l'adaptateur de visualisation
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":
# Tester le système d'analyse complet
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":
# Tester les erreurs de validation
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":
# Tester avec des données vides
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":
# Tester différents types de graphiques
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":
# Tester la gestion des extensions de fichiers
visualizer = LegacyChartGeneratorAdapter()
data = [1, 2, 3, 4]
visualizer.visualize(data)
# Tester divers formats de noms de fichiers
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":
# Tester le résumé de l'analyse
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":
# Tester avec des données numériques mixtes
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":
# Tester avec des données numériques invalides
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":
# Tester le résumé sans résultats
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":
# Tester avec un ensemble de données plus large
processor = LegacyDataAnalyzerAdapter()
data = list(range(1, 101)) # 1 à 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":
# Tester le flux de travail complet avec plusieurs opérations
processor = LegacyDataAnalyzerAdapter()
bar_visualizer = LegacyChartGeneratorAdapter("bar")
line_visualizer = LegacyChartGeneratorAdapter("line")
data = [12, 18, 25, 32, 28, 35, 42]
# Créer des systèmes d'analyse avec différents visualiseurs
bar_system = AnalyticsSystem(processor, bar_visualizer)
line_system = AnalyticsSystem(LegacyDataAnalyzerAdapter(), line_visualizer)
# Exécuter les analyses
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']}")
# Obtenir les résumés
print("Bar system summary:")
print(bar_system.get_analysis_summary())
print("Line system summary:")
print(line_system.get_analysis_summary())Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Object Oriented Programming
1Les fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe paramètre selfMéthodesAttributsMéthode constructeur (__init__)Récapitulatif - Calculatrice simple4Héritage
Héritage de baseLa fonction super()Redéfinition de méthodeHéritage multipleOrdre de résolution des méthodesRécapitulatif - Hiérarchie des employés7Méthodes spéciales
Introduction aux méthodes magiquesSurcharge d'opérateursMéthodes magiques de conteneurRécapitulatif - Liste personnalisée10Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FactoryPatron ObserverPatron Strategy2Décorateurs
Introduction aux décorateursDécorateur de propriétéDécorateur de méthode statiqueDécorateur de méthode de classe5Polymorphisme
Retour sur la redéfinition de méthodeDuck TypingClasses et méthodes abstraitesConception d'interfacesRécapitulatif - Calculateur de formes8Concepts avancés de la POO
Composition vs HéritageMixinsMéthodes statiques et de classeDécorateurs de classeGestionnaires de contexte11Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode TemplatePatron ÉtatPatron Composite3Propriétés de classe
Variables d'instance vs variables de classeDécorateurs de propriétéAttributs privésRécapitulatif - Gestionnaire de compte bancaire