Adapter/Wrapperパターンとは？

※最近デザインパターンを勉強し直そうと思って読んだ[この書籍](https://www.hyuki.com/dp/)を自分なりにまとめていっている記事です。
※この記事ではAdapter/Wrapperパターン（以後、Adapterパターンと記載します）について書いています。

異なるライブラリやAPIが混在している、使用していたライブラリを変更したが影響する箇所が思ったよりも多いなどで頭を悩ませたことはありませんか？例えば、古いプリンターAPIを新しいシステムに組み込みたいが、そのままでは互換性がない…。そんなときに強力な解決策となるのが、Adapterパターン（Wrpperパターンともいう）です。

この記事では、Adapterパターンの基本概念から、Javaでの具体的な実装方法を説明します。

## Adapterパターンとは？

Adapterパターンは、既存のクラスやライブラリが提供するインターフェースを別のインターフェースに変換し、互換性のないクラス同士を連携させるデザインパターンです。これにより、既存コードを修正することなく、新しいシステムで再利用することが可能になります。

### 典型的な適用シーン
- レガシーシステムのモダナイゼーション: 古いAPIやライブラリを新しいシステムで再利用
- サードパーティライブラリの統合: 期待するインターフェースと異なるライブラリをシステムに適合させる

### Adapterパターンの基本構造
```plaintext
  クライアント      ----->  ターゲットインターフェース
       |                         ^
       |                         |
  旧クラス --------->  Adapter ---------> 新クラス
```

### 他のデザインパターンとの関係
Adapterパターンは、FacadeパターンやDecoratorパターンとよく比較されます。Facadeはシステムの単純化を、Decoratorは機能の拡張を目的としますが、Adapterは互換性の問題を解決するために使用されます。

## Javaでの実装例

### 1. 既存のクラス (LegacyPrinter)

このクラスは古いAPIで提供されており、新しいシステムではそのまま使うことができません。
```java
public class LegacyPrinter {
    public void printOldFormat(String text) {
        System.out.println("Legacy Printer: " + text);
    }
}
```
※ LegacyPrinter クラスは、旧式のフォーマットでプリントする機能を提供しています。

### 2. 新しいインターフェース (ModernPrinter)

新しいシステムで期待される標準的なインターフェースです。
```java
public interface ModernPrinter {
    void print(String text);
}
```

※ ModernPrinter インターフェースは、モダンなプリンタークラスが実装すべきメソッドを定義しています。

### 3. Adapterクラスの実装

LegacyPrinter クラスを ModernPrinter インターフェースに適合させるためのAdapterクラスを作成します。

```java
public class PrinterAdapter implements ModernPrinter {
    private LegacyPrinter legacyPrinter;

    public PrinterAdapter(LegacyPrinter legacyPrinter) {
        this.legacyPrinter = legacyPrinter;
    }

    @Override
    public void print(String text) {
        // 旧メソッドを呼び出し、新しいインターフェースに適合させる
        legacyPrinter.printOldFormat(text);
    }
}
```

※ PrinterAdapter は、LegacyPrinter をラップし、新しい ModernPrinter インターフェースを実装します。

### 4. Adapterの使用例

以下の例では、LegacyPrinter クラスを ModernPrinter インターフェースとして使用しています。
```java
public class AdapterPatternExample {
    public static void main(String[] args) {
        LegacyPrinter legacyPrinter = new LegacyPrinter();
        ModernPrinter printer = new PrinterAdapter(legacyPrinter);

        printer.print("Hello, Adapter Pattern!");
    }
}
```
※ PrinterAdapter を介して、旧式のプリンターAPIを新しいインターフェースで使用しています。


### 5. 他のシナリオでのAdapterパターンの使用

#### 異なるデータフォーマット間の変換

例えば、XMLデータを扱う古いクラスを、JSON形式のデータを扱う新しいシステムで使用する場合にAdapterパターンを適用できます。この場合、データのフォーマット変換を行うAdapterクラスを実装することで、システム全体をモダンな形式に対応させることができます。


#### GUIライブラリの統合

異なるGUIフレームワーク間でのイベントハンドリングの統合も、Adapterパターンを使って行うことができます。これにより、既存のコードを大幅に変更することなく、新しいUI要件に対応できます。


## Adapterパターンのベストプラクティスとアンチパターン

### ベストプラクティス
- シンプルなAdapterを作成する: Adapterクラスはできるだけシンプルに保つべきです。複雑な変換ロジックは、別のヘルパークラスに分離することを検討してください
- 一貫性のある命名規則を使う: クラス名やメソッド名は、その役割を明確に反映するようにします

### アンチパターン
- Adapterの濫用: すべての互換性の問題をAdapterで解決しようとすると、クラス階層が複雑になり、コードの可読性や保守性が低下する恐れがあります。必要に応じて、他のデザインパターンやリファクタリングを検討しましょう。
- 過剰な依存関係の導入: Adapterを使用することで、クラス間の依存関係が増えることがあります。依存関係が過剰になると、システム全体の柔軟性が損なわれるため、適度に設計することが重要です。


## Adapterパターンを導入する手順

- 1.既存クラスと新しいインターフェースを分析する:
	•	まず、Adapterを作成する前に、既存クラス（例: LegacyPrinter）と新しいインターフェース（例: ModernPrinter）の違いを明確にし、どの部分を適合させる必要があるかを把握します。
- 2.シンプルなAdapterクラスを設計する:
	•	必要最低限のメソッドのみをAdapterクラスに実装し、コードの複雑化を避けます。
- 3.テストを実施する:
	•	Adapterクラスの動作を確認するためのユニットテストを作成し、既存のコードが新しいインターフェースで正しく動作することを検証します。
- 4.段階的な統合を行う:
	•	全ての旧クラスを一度に新しいインターフェースに適合させるのではなく、段階的にAdapterを導入し、システム全体への影響を最小限に抑えます。
- 5.コードレビューを実施する:
	•	Adapterを使用したコードが適切かどうか、他の開発者とレビューを行い、ベストプラクティスに従っているか確認します。

## まとめ

Adapterパターンは、異なるインターフェースを持つクラスを連携させるための強力なツールです。特に、レガシーシステムを新しい環境に統合する際などに、その威力を発揮します。
次の記事では別のデザインパターンについてまとめていきます🔥