Menu
Coddy logo textTech

어댑터 패턴

Coddy Python 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 64개 중 50번째.

어댑터 패턴(Adapter Pattern)은 호환되지 않는 인터페이스를 가진 객체들이 함께 작동할 수 있도록 해줍니다. 이는 클라이언트가 기대하는 새로운 인터페이스로 기존 클래스를 감싸줌으로써 브리지(bridge) 역할을 합니다.

호환되지 않는 인터페이스를 가진 두 시스템이 있습니다:

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

핵심 포인트: 어댑터 패턴(Adapter Pattern)은 기존 클래스를 새로운 인터페이스로 래핑하여 호환되지 않는 인터페이스들이 함께 작동하도록 만듭니다. 어댑터는 기대하는 인터페이스의 호출을 래핑된 객체의 실제 인터페이스로 변환합니다. 이는 기존 코드를 수정하지 않고 레거시 코드나 서드파티 라이브러리를 통합하는 데 유용합니다.

challenge icon

챌린지

중급

이 챌린지에서는 어댑터(Adapter) 디자인 패턴을 구현하여 레거시 시스템을 현대적인 애플리케이션 아키텍처와 통합하는 작업을 수행합니다. 어댑터 패턴은 한 인터페이스를 다른 인터페이스로 변환하는 래퍼(어댑터)를 생성함으로써, 호환되지 않는 인터페이스를 가진 객체들이 함께 작동할 수 있도록 해줍니다.

 

귀하의 회사는 새로운 현대적 분석 플랫폼과 통합해야 하는 레거시 데이터 분석 및 시각화 시스템을 보유하고 있습니다. 레거시 컴포넌트들은 새로운 시스템과 호환되지 않는 인터페이스를 가지고 있습니다. 레거시 코드를 다시 작성하는 대신(이는 위험하고 비용이 많이 듭니다), 귀하는 이러한 컴포넌트들이 새로운 시스템과 작동하도록 만드는 어댑터를 생성하는 임무를 맡았습니다.

수행해야 할 작업은 다음과 같습니다:

  1. interfaces.py에 정의된 현대적 인터페이스를 이해합니다 (이 파일은 수정할 수 없습니다).
  2. legacy_system.py에 있는 레거시 컴포넌트들을 검토합니다 (이 파일은 수정할 수 없습니다).
  3. adapters.py에 레거시 컴포넌트들을 현대적 인터페이스와 호환되게 만드는 어댑터 클래스들을 구현합니다.
  4. modern_system.py에서 이러한 인터페이스를 사용하는 현대적 시스템을 생성합니다.

치트 시트

어댑터 패턴(Adapter Pattern)은 기존 클래스를 클라이언트가 기대하는 새로운 인터페이스로 래핑하여, 호환되지 않는 인터페이스를 가진 객체들이 함께 작동할 수 있도록 합니다.

기본적인 어댑터 구조:

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의 모든 레슨