Menu
Coddy logo textTech

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.mp4

Point 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.

challenge icon

Défi

Moyen

Dans 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 :

  1. Comprendre les interfaces modernes définies dans interfaces.py (ce fichier ne peut pas être modifié)
  2. Examiner les composants hérités dans legacy_system.py (ce fichier ne peut pas être modifié)
  3. Implémenter des classes d'adaptateurs dans adapters.py qui rendent les composants hérités compatibles avec les interfaces modernes
  4. Créer un système moderne dans modern_system.py qui 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: Hello

Exemple 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())
quiz iconTestez-vous

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