Menu
Coddy logo textTech

Паттерн Адаптер

Часть раздела Объектно-ориентированное программирование путешествия по Python на Coddy. Урок 50 из 64.

Паттерн «Адаптер» позволяет объектам с несовместимыми интерфейсами работать вместе. Он действует как мост, оборачивая существующий класс новым интерфейсом, который ожидают клиенты.

Вот две системы с несовместимыми интерфейсами:

class OldPrinter:
    def old_print(self, text):
        return f"OLD: {text}"

class NewPrinter:
    def print(self, text):
        return f"NEW: {text}"

Старый принтер использует old_print(), а новый — print().

Создайте адаптер, чтобы старый принтер работал с новым интерфейсом:

class PrinterAdapter:
    def __init__(self, old_printer):
        self.old_printer = old_printer
    
    def print(self, text):
        # Адаптировать старый интерфейс к новому интерфейсу
        return self.old_printer.old_print(text)

Адаптер оборачивает старый принтер и предоставляет ожидаемый интерфейс.

Используйте оба принтера с одним и тем же клиентским кодом:

def print_document(printer, text):
    return printer.print(text)  # Ожидает метод print()

# Использовать новый принтер напрямую
new_printer = NewPrinter()
print(print_document(new_printer, "Hello"))

# Использовать старый принтер через адаптер
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)
print(print_document(adapter, "Hello"))

Создадим ещё один пример с медиаплеерами:

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"))

Вывод:

NEW: Hello
OLD: Hello
Playing MP3: song.mp3
Playing MP4: video.mp4

Ключевой момент: шаблон «Адаптер» позволяет несовместимым интерфейсам работать вместе, оборачивая существующий класс новым интерфейсом. Адаптер преобразует вызовы ожидаемого интерфейса в вызовы фактического интерфейса обёрнутого объекта. Это полезно для интеграции устаревшего кода или сторонних библиотек без изменения существующего кода.

challenge icon

Задание

Средне

В этом задании вам предстоит реализовать паттерн проектирования «Адаптер», чтобы интегрировать устаревшие системы с современной архитектурой приложения. Паттерн «Адаптер» позволяет объектам с несовместимыми интерфейсами работать вместе, создавая оболочку (адаптер), которая преобразует один интерфейс в другой.

 

В вашей компании есть устаревшая система анализа и визуализации данных, которую необходимо интегрировать с новой современной аналитической платформой. Интерфейсы устаревших компонентов несовместимы с новой системой. Вместо переписывания устаревшего кода (что было бы рискованно и дорого) вам поручено создать адаптеры, чтобы эти компоненты могли работать с новой системой.

Вам необходимо:

  1. Изучить современные интерфейсы, определённые в interfaces.py (этот файл нельзя изменять)
  2. Изучить устаревшие компоненты в legacy_system.py (этот файл нельзя изменять)
  3. Реализовать классы-адаптеры в adapters.py, которые сделают устаревшие компоненты совместимыми с современными интерфейсами
  4. Создать современную систему в modern_system.py, использующую эти интерфейсы

Попробуйте сами

# Импорт всех необходимых классов
from adapters import LegacyDataAnalyzerAdapter, LegacyChartGeneratorAdapter
from modern_system import AnalyticsSystem

# Комплексный обработчик тестовых случаев
test_case = input()

if test_case == "basic_adapter_test":
    # Тест базовой функциональности адаптера
    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":
    # Тест адаптера визуализатора
    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":
    # Тест полной аналитической системы
    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":
    # Test validation errors
    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":
    # Тест с пустыми данными
    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":
    # Тест различных типов диаграмм
    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":
    # Тест обработки расширений файлов
    visualizer = LegacyChartGeneratorAdapter()
    data = [1, 2, 3, 4]
    visualizer.visualize(data)
    
    # Тест различных форматов имён файлов
    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":
    # Test analysis summary
    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":
    # Тест со смешанными числовыми данными
    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":
    # Тест с недопустимыми числовыми данными
    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":
    # Тест сводки без результатов
    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":
    # Тест с большим набором данных
    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":
    # Test complete workflow with multiple operations
    processor = LegacyDataAnalyzerAdapter()
    bar_visualizer = LegacyChartGeneratorAdapter("bar")
    line_visualizer = LegacyChartGeneratorAdapter("line")
    
    data = [12, 18, 25, 32, 28, 35, 42]
    
    # Создать системы аналитики с разными визуализаторами
    bar_system = AnalyticsSystem(processor, bar_visualizer)
    line_system = AnalyticsSystem(LegacyDataAnalyzerAdapter(), line_visualizer)
    
    # Запустить анализы
    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']}")
    
    # Получить сводки
    print("Bar system summary:")
    print(bar_system.get_analysis_summary())
    print("Line system summary:")
    print(line_system.get_analysis_summary())
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Python