2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

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

Adapterパターンについて

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

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

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

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

Adapterパターンの実装例

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

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

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

クラス図

スクリーンショット 2025-01-05 230051.png

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

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

    public void turnOff(){
        System.out.println("Light is turned OFF");
    }
}
Switch.java
class Switch{
    private Light light;

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

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

    public void pressOff(){
        light.turnOff();
    }
}
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

ソースコード

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

ソースコード

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");
    }
}
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
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

ソースコード

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

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

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

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

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

実装例3(オブジェクト形式のAdapterパターン)

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

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

クラス図

スクリーンショット 2025-01-05 234812.png

ソースコード

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();
    }
}
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

ソースコード

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

ソースコード

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
// extendsしていることに注目
class LightAdapter extends Light implements Switchable {

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

    @Override
    public void turnOff() {
        super.turnOff();
    }
}
Switch.java
class Switch {
    private Switchable device;

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

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

    public void pressOff() {
        device.turnOff();
    }
}
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パターン)で十分機能します.

最後に

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

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

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

参考

アジャイルソフトウェア開発の奥義

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?