Menu
Coddy logo textTech

アダプターパターン

CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 50/64。

アダプターパターンは、互換性のないインターフェースを持つオブジェクト同士を連携させるためのパターンです。既存のクラスをクライアントが期待する新しいインターフェースでラップすることで、架け橋として機能します。

互換性のないインターフェースを持つ2つのシステムがあります:

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 で、これらのインターフェースを使用する最新のシステムを作成する

チートシート

アダプターパターン(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のすべてのレッスン