読者です 読者をやめる 読者になる 読者になる

datchの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【デザインパターン】アダプタパターン

デザインパターン プログラミング

とあるバグに数時間悩んでいたのですが、その原因がDBにNULLではなく空文字が入っていることでUNIQUEでエラーしてた。
メンターの方に指摘された時は、ああぁぁぁぁってなりました。

それでは今週は、デザインパターンの【Adaptor】についての記事を書いていきます。

参考書



さらっと読める「OREILLY Head First デザインパターン」を参考にしながら書いています。
しばらくはこいつを参考にデザインパターンについて書いていくよ!

一覧



この記事は以下の様な流れで記述される。

  1. Adaptorパターンって何ですか?
  2. Adaptorパターンのメリット
  3. 身近で簡単な使用例
  4. Adaptorパターンを使う

Adaptorパターンって何ですか?



Adaptorパターンとは、2つの異なる互換性のないクラスを、連携させるパターンです。
本来のクラスのインターフェースとは、異なるインターフェースを提供させる、つまりはインターフェースを変更する事が可能。
他の名称で「Wrapper」クラスとも呼ばれたりします。

Wikipediaさんも参照。

Adapter パターンを用いると、既存のクラスに対して修正を加えることなく、インタフェースを変更することができる。Adapter パターンを実現するための手法として継承を利用した手法と委譲を利用した手法が存在する。それぞれについて以下の節で説明する。

継承を利用したパターンのクラス図
継承形式のadaptorパターンクラス図

委譲を利用したパターンのクラス図
委譲形式のadaptorパターンクラス図

Adaptorパターンのメリット



このパターンを使用するメリットは、本来使用出来ない、関連の無いはずのクラスを同様に使える(もしくは似たような動作を提供する)ようになる事。
これに尽きる

身近で簡単な例



参考書では身近なAdaptorの例を以下の様に示しています。

アメリカ製のノートPCをヨーロッパで使うことになった場合、普通は海外用のコンセントに変換するアダプタを使用して、ヨーロッパのコンセントのインターフェースをアメリカ製の物に変換して利用する。

一枚のAdaptorクラスを噛ませることで異なるクラス間(アメリカ製のプラグが、ヨーロッパ製のコンセント)なのに使用出来るようになった。
これがアダプタの全ての役割を示しています。
シンプルにいえば、インターフェースを変更する、ただそれだけのデザインパターンです。

委譲を利用するクラス図で言えば、以下のように対応しています。

簡単な例での登場人物クラス図との対応
アメリカの電源プラグTarget
変換アダプタAdaptor
ヨーロッパのコンセントAdaptee

JavaでのAdaptorパターン

またJavaなどでもプリミティブな型をオブジェクト型として扱える様にするためのラッパークラスが準備されているのはご存知でしょうか?
その説明はこちらのサイトでわかりやすくされているので、リンクを載せておきます。
これも本質的には似たような動作をしているのがわかると思います。
10-8. ラッパークラス - マンガで分かる Java入門講座 - マンガPG

Adaptorパターンを使う



ここでは、一章の「デザインパターン入門」での鴨の実装を例に見てみましょう。

下記は一章で鴨クラスを実装したものです。

public class Duck
{
    public void quack();
    public void fly();
}

public class MallardDuck implements Duck
{
    public void quack()
    {
        System.outprintln("ガーガー");
    }

    public void fly()
    {
        System.out.println("飛んでいます");
    }
}

そして、ここで新入りの鳥として七面鳥を登場させます。
以下は七面鳥のベースクラスと、それをimplementsした具象実装クラスです。

public class Turkey
{
    public void gobble(); // 鳴き声を上げる関数名がDuckクラスと異なる事に注目して下さい。
    public void fly();
}

public class WildTurkey implements Turkey
{
    public void gobble()
    {
        System.out.println("ゴロゴロ");
    }

    public void fly()
    {
        System.out.println("短い距離を飛んでいます");
    }
}

さて、ここでAdaptorパターンを使用して、七面鳥をあたかも鴨に見せてみましょう。

public class TurkeyAdapter implements Duck
{
    Turkey turkey;
    
    public TurkeyAdapter(Turkey turkey)
    {
        this.turkey = turkey;
    }

    public void quack()
    {
        turkey.gobble();
    }

    public void fly()
    {
        for(int i = 0; i < 5; ++i)
        {
            turkey.fly();
        }
    }
}

このクラスを見て頂けると分かると思いますが、quack()によって本来違うメソッド名をあたかも同じ関数として呼び出すことが出来ます。
では、実際に上記のアダプターを呼び出して見ましょう。

public class DuckTestDrive{
    public static void main(string[] args)
    {
        MallardDuck duck = new MallardDuck();

        WildTurkey turkey = new WildTurkey();
        Duck turkeyAdapter = new TurkeyAdapter(turkey);
        // (1)
        turkey.gobble();
        turkey.fly();
        // (2)
        testDuck(duck);
        // (3)
        testDuck(turkeyAdapter);
    }

    static void testDuck(Duck duck){
        duck.quack();
        duck.fly();
    }
}

さて、まず(1)で七面鳥を普通に呼び出しています。
そして、(2)でduckクラスを普通に呼び出します。
そして、最後に(3)でturkeyをアダプタにセットして呼びすことが出来ます。
(2)と同様に(3)でもTurkeyをあたかもDuckであるかのように動作させることが出来るのを確認することが出来ました。

これがAdaptorパターンです。
ちなみに今回紹介したのはオブジェクトアダプタというもので、もう1つのクラスアダプタでは多重継承が出来ないJavaでは扱う事が出来ないので解説しません。
ただ、本質的に出来る事は一緒なのでオブジェクトアダプタを知っているだけで十分だと思います。

最後に



Adaptorパターンどうでしたか。
こいつを使う場合は、後から急な仕様の変更が来た時にその仕様の違いを吸収するのに使ったりと、簡単で色々と応用出来る場所は多いと思います。
自分も似たような感じで使っている場面が多かったので、覚えておいて損は無いと思います!