Adapterパターン

# はじめに
Gofパターンの一つで構造に関するパターンである,
Adapterパターンについて見ていきます.

# Adapterパターンについて
Adapterパターンは,互換性のないインタフェースを持つクラス同士をつなげて
協調動作させることです.

電源「Adapter」のように仲介する役割を持ったクラスが存在する
デザインパターンです.

こうすることにより,2つのオブジェクト間に**関連性**を持たせることができます.

また,Adapterパターンには**オブジェクト形式(委譲を利用)** を利用したものと,
**クラス形式(`継承を利用)** を利用したものがあります.

# Adapterパターンの実装例
Adapterパターンの実装例について考えていきたいと思います.

## 実装例1(Adapterパターン未適用)

まず,スイッチに対してONとOFFの指示を出してライトにONとOFFを通達するという設定の
以下のような設計を考えてみます.

#### クラス図
![スクリーンショット 2025-01-05 230051.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/78d7ecd9-52c2-02e2-96d7-f68d67d542fc.png)

この場合のコードは以下のように記述します.

```java:Light.java
class Light{
    public void turnOn(){
        Sustem.out.println("Light is turned ON");
    }

    public void turnOff(){
        System.out.println("Light is turned OFF");
    }
}
```

```java:Switch.java
class Switch{
    private Light light;

    public Switch(Light light){
        this.light = light;
    }

    public void pressOn(){
        light.turnOn();
    }

    public void pressOff(){
        light.turnOff();
    }
}
```

```java:SwitchExample.java
public class SwitchExample{
    public static void main(String[] args){
        Light light = new Light();
        Switch lightSwitch = new Switch(light);

        lightSwitch.pressOn();
        lightSwitch.pressOff();
    }
}
```

### 問題点
この場合,以下のような問題点が考えられます.
- Switchクラスが直接Lightクラスに依存し,**柔軟性が欠如**
(`依存関係逆転の原則`・`オープン・クローズドの原則`に反します.)
- Lightに直接依存することで,異なる種類のデバイスを**一貫した方法で扱えない**
(別のデバイスでSwitchが必要なときに,Lightも一緒に連れていくことになります)

...この説明だけではわかりづらいので具体例もあげて考えてみます.

### Fanクラスを追加
具体的にFanクラスを追加することを考えてみます.

Fanクラスを追加した場合,
クラス図とソースコードは以下のようになります.

#### クラス図
![スクリーンショット 2025-01-06 013058.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/742a11f5-139a-91cc-a107-df60e369a6dd.png)

#### ソースコード
```java:Fanの追加
// Light.java
class Light {
    public void turnOn() {
        System.out.println("Light is turned on");
    }

    public void turnOff() {
        System.out.println("Light is turned off");
    }
}

// Fan.java
class Fan {
    public void start() {
        System.out.println("Fan is started");
    }

    public void stop() {
        System.out.println("Fan is stopped");
    }
}

// Switch.java
class Switch {
    private Light light; 
    private Fan fan;     

    public Switch(Light light, Fan fan) {
        this.light = light;
        this.fan = fan;
    }

    public void turnOnLight() {
        light.turnOn();
    }

    public void turnOffLight() {
        light.turnOff();
    }

    public void turnOnFan() {
        fan.start();
    }

    public void turnOffFan() {
        fan.stop();
    }
}

// SwitchExample.java
public class SwitchExample {
    public static void main(String[] args) {
        Light light = new Light();
        Fan fan = new Fan();

        Switch sw = new Switch(light, fan);
        sw.turnOnLight(); 
        sw.turnOnFan();  
        sw.turnOffLight(); 
        sw.turnOffFan();  
    }
}
```

Fanクラスを追加した結果,発生する問題点は具体的には以下の通りになります.
- 新しいデバイスを追加するたび,Switchクラスに新しいコードを追加
(**拡張性**が低い)
- SwitchがLightやFanの具体的なクラスに依存することで,Switchクラスに修正が必要
(**結合度**が高い)
- LightとFanで電源をOn,Offにするメソッドが異なる可能性
(**一貫性**が欠如)

このように,あるクラス(Switch)が具体的なクラス(Light)に依存することで,
様々な問題点が生じてしまいました.

## 実装例2(Abstract Serverパターン)
Adapterパターンを適用せずとも,**SwitchとLightの間にインタフェースを導入**することで
実は`依存関係逆転の原則`と`オープン・クローズドの原則`は満たすことができます.

したがって,上記のような問題を解決することができます.

クラス図とソースコードは以下のようになります.
(以下のような構成をAbstract Serverパターンと呼ぶみたいです)
#### クラス図
![スクリーンショット 2025-01-06 002617.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/82558002-b44f-e4f2-76a0-c72c11b7a4b5.png)

#### ソースコード
```java:Switchable.java
interface Switchable {
    void turnOn();
    void turnOff();
}
```

```java:Light.java
class Light implements Switchable {
    @Override
    public void turnOn() {
        System.out.println("Light is turned on");
    }

    @Override
    public void turnOff() {
        System.out.println("Light is turned off");
    }
}
```

```java:Switch.java
class Switch {
    private Switchable device;

    // コンストラクタでSwitchableデバイスを受け取る
    public Switch(Switchable device) {
        this.device = device;
    }

    public void turnOn() {
        device.turnOn();
    }

    public void turnOff() {
        device.turnOff();
    }
}
```

```java:SwitchExample2.java
public class SwitchExample2 {
    public static void main(String[] args) {
        // LightをSwitchableとして扱う
        Switchable light = new Light();

        // Switchでデバイスを操作
        Switch lightSwitch = new Switch(light);

        // Lightの操作
        lightSwitch.turnOn();  // "Light is turned on"
        lightSwitch.turnOff(); // "Light is turned off"
    }
}
```

本当に問題が解決しているかどうかをFanクラスを追加して,
見ていきましょう.

### Fanクラスの追加
以下がクラス図とソースコードになります.
#### クラス図
![スクリーンショット 2025-01-06 013253.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/209ed653-c213-f1ee-1eee-7f91b6ce9602.png)

#### ソースコード
```java:Fanクラスの追加
// Switchable.java
// 変更なし
interface Switchable {
    void turnOn();
    void turnOff();
}

// Light.java
// 変更なし
class Light implements Switchable {
    @Override
    public void turnOn() {
        System.out.println("Light is turned on");
    }

    @Override
    public void turnOff() {
        System.out.println("Light is turned off");
    }
}

// Fan.java
// Fanクラスを追加
class Fan implements Switchable {
    @Override
    public void turnOn() {
        System.out.println("Fan is started");
    }

    @Override
    public void turnOff() {
        System.out.println("Fan is stopped");
    }
}

// Switch.java
// 変更なし
class Switch {
    private Switchable device;

    // コンストラクタでSwitchableデバイスを受け取る
    public Switch(Switchable device) {
        this.device = device;
    }

    public void turnOn() {
        device.turnOn();
    }

    public void turnOff() {
        device.turnOff();
    }
}

// SwitchExample2.java
// Fanクラスを追加
public class SwitchExample2 {
    public static void main(String[] args) {
        
        Switchable light = new Light();
        Switchable fan = new Fan();

        Switch lightSwitch = new Switch(light);
        Switch fanSwitch = new Switch(fan);

        lightSwitch.turnOn();  
        lightSwitch.turnOff(); 

        fanSwitch.turnOn();   
        fanSwitch.turnOff();   
    }
}
```

Fanクラスを追加しても,新しいコードの追加や修正が**最低限**となり,
上記のような問題点が解決できたことを確認出来ました.

### 問題点...?
**「Adapterパターンを適用せずとも問題が解決したし,これを用いればよいのでは?」**
と思う方もいると思います.

...実はこれは`単一責任の原則`に反する問題が発生しています.

LightとSwitchableには継承の関係があります.

#### LightとSwitchableの関係
![スクリーンショット 2025-01-06 005252.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/d6dae667-e6a8-82d0-57b7-7f0f3cd30ab4.png)

この関係により,既存のクラスや外部のクラスを利用する際,
**柔軟性に欠ける**問題が発生します.

例えば,外注して手に入れたLightクラスへ,
新しいインタフェースを追加することはできません.

さらに,エアコンやテレビなど,Switchで制御したいほかの機器のクラスがあっても
それらに既に継承関係がある場合,直接Swichableから継承させることはできません.
(多重継承ができるなら関係ないかもしれませんが)

つまり,**すでに決まった仕組みやルールがあるクラスに
Switchableインタフェースを直接追加することができない**ことを意味します.

## 実装例3(オブジェクト形式のAdapterパターン)
では,上記のような問題点を解決するため,
オブジェクト形式のAdapterパターンを適用してみましょう.

以下がクラス図とソースコードになります.

#### クラス図
![スクリーンショット 2025-01-05 234812.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/d3994980-5d45-7139-44e7-ca295cfc74a5.png)

#### ソースコード
```java:Switchable.java
interface Switchable {
    void turnOn();
    void turnOff();
}
```

```java:Light.java
class Light {
    public void turnOn() {
        System.out.println("Light is turned ON");
    }

    public void turnOff() {
        System.out.println("Light is turned OFF");
    }
}
```

```java:LightAdapter.java
class LightAdapter implements Switchable {
    private Light light;

    public LightAdapter(Light light) {
        this.light = light;
    }

    @Override
    public void turnOn() {
        light.turnOn();
    }

    @Override
    public void turnOff() {
        light.turnOff();
    }
}
```

```java;Switch.java
class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void pressOn() {
        device.turnOn();
    }

    public void pressOff() {
        device.turnOff();
    }
}
```

```ObjectAdapterExample.java
public class ObjectAdapterExample {
    public static void main(String[] args) {
        Light light = new Light();
        Switchable adapter = new LightAdapter(light); 
        Switch lightSwitch = new Switch(adapter);

        lightSwitch.pressOn(); 
        lightSwitch.pressOff(); 
    }
}
```

このオブジェクト形式のAdapterパターンの特徴は以下のようになります.
- AdapterクラスがLightのインスタンスを**委譲**によって操作
- **柔軟**に複数のクラスや構成に対応可能

Adapterパターンを適用することで,適切なAdaprterを作成するだけで,
ONとOFFできるオブジェクトであればどんなものでもSwitchは扱えます.

また,**Switchableが持っているメソッドと同じ名前のメソッドを
オブジェクトが持つ必要もありません**.

これは,Adapterがそのオブジェクトのインタフェースを
Switchableのインタフェースに変換してくれるためです.

### Fanクラスの追加
では,(しつこいですが)こちらにもFanクラスを追加してみましょう.
以下がクラス図とソースコードになります.

#### クラス図
![スクリーンショット 2025-01-06 014850.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/6cbf5ffe-4543-cab5-9087-d8097dd2d549.png)




#### ソースコード
```java:Fanクラスの追加
// Switchable.java
// 変更なし
interface Switchable {
    void turnOn();
    void turnOff();
}

// Light.java
// 変更なし
class Light {
    public void turnOn() {
        System.out.println("Light is turned ON");
    }

    public void turnOff() {
        System.out.println("Light is turned OFF");
    }
}

// LightAdapter.java
// 変更なし
class LightAdapter implements Switchable {
    private Light light;

    public LightAdapter(Light light) {
        this.light = light;
    }

    @Override
    public void turnOn() {
        light.turnOn();
    }

    @Override
    public void turnOff() {
        light.turnOff();
    }
}

// Fan.java
// Fanクラスの追加
// メソッド名がLightと違うことに注目
class Fan {
    public void start() {
        System.out.println("Fan is spinning");
    }

    public void stop() {
        System.out.println("Fan is stopped");
    }
}

// FanAdapter.java
// FanAdapterの追加
// FanクラスのメソッドをturnOnメソッドに変換
class FanAdapter implements Switchable {
    private Fan fan;

    public FanAdapter(Fan fan) {
        this.fan = fan;
    }

    @Override
    public void turnOn() {
        fan.start();
    }

    @Override
    public void turnOff() {
        fan.stop();
    }
}

// Switch.java
// 変更なし
class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void pressOn() {
        device.turnOn();
    }

    public void pressOff() {
        device.turnOff();
    }
}

// ObjectAdapterExample.java
// Fanクラスを追加
public class ObjectAdapterExample {
    public static void main(String[] args) {
        Light light = new Light();
        Switchable adapter = new LightAdapter(light);
        Switch lightSwitch = new Switch(adapter);

        lightSwitch.pressOn();
        lightSwitch.pressOff();

        Fan fan = new Fan();
        Switchable fanAdapter = new FanAdapter(fan); 
        Switch fanSwitch = new Switch(fanAdapter);

        System.out.println("\nUsing the Fan:");
        fanSwitch.pressOn();
        fanSwitch.pressOff();
    }
}
```
Fanオブジェクトは**Switchableが持っているメソッドと同じ名前にする必要がない**ことや,
実装例2における問題を解決できることが確認できたと思います.

## 実装例4(クラス形式)
最後にクラス形式のAdapterパターンを適用してみます.

以下がクラス図とソースコードになります.
(Fanクラスを追加する場合は省略します)

#### クラス図
![スクリーンショット 2025-01-05 234816.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/3ea0fb9c-d738-6ee7-cd0c-b9dd52f95bd7.png)


#### ソースコード
```java:Switchable.java
interface Switchable {
    void turnOn();
    void turnOff();
}
```

```java:Light.java
class Light {
    public void turnOn() {
        System.out.println("Light is turned ON");
    }

    public void turnOff() {
        System.out.println("Light is turned OFF");
    }
}
```

```java:LightAdapter.java
// extendsしていることに注目
class LightAdapter extends Light implements Switchable {

    @Override
    public void turnOn() {
        super.turnOn();
    }

    @Override
    public void turnOff() {
        super.turnOff();
    }
}
```

```java:Switch.java
class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void pressOn() {
        device.turnOn();
    }

    public void pressOff() {
        device.turnOff();
    }
}
```

```java:ClassAdapter.java
public class ClassAdapterExample {
    public static void main(String[] args) {
        // Lightインスタンスを作っていない
        Switchable adapter = new LightAdapter();
        Switch lightSwitch = new Switch(adapter);

        lightSwitch.pressOn();
        lightSwitch.pressOff();
    }
}
```

このクラス形式のAdapterパターンの特徴は以下のようになります.
- AdapterクラスがLightを**継承**
- コードがシンプルだが,**柔軟性が低い**
- オブジェクト形式よりも効率と使い勝手がよくなる

# Adapterパターンは高コスト
最後にAdapterパターンを適用する上での注意点を見てみます.

それは**高コスト**であることです.

理由は以下のようなものが考えられます
- 新たなクラスを作成する必要性
- Adapterのインスタンスの作成と,オブジェクトの結合
- Adapterの起動によるメモリスペースの消費

そのため,今回の例でいうとSwitchが制御するオブジェクトがLight以外に
出てこない限り,実装例2(Abstract Serverパターン)で十分機能します.

# 最後に
前回までと比べると少し記事が長くなってしまいました.

ただ,**発生しうる問題に対し,具体例を交えた説明をすること**で,
デザインパターンを導入し,問題をどのように解決するかのイメージをすることができました.

そのため,今後の記事にも,この説明方法は取り入れていきたいと思います.

# 参考
[アジャイルソフトウェア開発の奥義](https://www.amazon.co.jp/%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E3%81%AE%E5%A5%A5%E7%BE%A9-%E7%AC%AC2%E7%89%88-%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E9%96%8B%E7%99%BA%E3%81%AE%E7%A5%9E%E9%AB%84%E3%81%A8%E5%8C%A0%E3%81%AE%E6%8A%80-%E3%83%AD%E3%83%90%E3%83%BC%E3%83%88%E3%83%BBC%E3%83%BB%E3%83%9E%E3%83%BC%E3%83%81%E3%83%B3/dp/4797347783)