Menu
Coddy logo textTech

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

Часть раздела Object Oriented Programming путешествия по 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

Задание

Средне

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

 

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

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

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

Шпаргалка

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

Базовая структура адаптера:

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

Пример с несовместимыми интерфейсами принтеров:

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

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

# Клиентский код ожидает метод print()
def print_document(printer, text):
    return printer.print(text)

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

Пример адаптера медиаплеера:

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)

Адаптер транслирует вызовы из ожидаемого интерфейса в фактический интерфейс обернутого объекта, обеспечивая интеграцию устаревшего кода без его изменения.

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

# Импорт всех необходимых классов
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":
    # Тестирование ошибок валидации
    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":
    # Тестирование сводки анализа
    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":
    # Тестирование полного рабочего процесса с несколькими операциями
    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Проверьте себя

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

Все уроки раздела Object Oriented Programming