101010

とあるアプリ開発者のブログです。KotlinやSwiftが好き。たまにポえむってます。

Adapter パターンを理解する

増補改訂版Java言語で学ぶデザインパターン入門 を読み始めた。ここ数ヶ月で学びたかったことが何だったのか、ようやく見えてきた気がする。つまり私はデザインパターンを学びたかったのだ。

色々なプログラミングの本をあさってはみてきたものの、そのプログラミングがどうしてそういう書き方になっているのかイマイチ理解できなかった。それもそのはず、デザインパターンというのを理解していなかったからだろう。本書では23ものデザインパターンを紹介しているので、一つ一つゆっくり理解しながら読み進めて行きたいと思う。

今回は第2章の、Adapterパターンについて触れてみたいと思う。なお、プログラミング言語はKotlinで、IntelliJで実行している。

Adapter パターンとは

Adapterパターンというのを、物質世界でたとえてみよう。

100V電源につなぐアダプターをイメージしてほしい。例えばUSBアダプター。100Vを5Vに変換するものだ。5Vの先にはスマホや、モバイルバッテリー、はたまたLEDライトなどと今ではたくさんの機器がUSBアダプターへ接続可能となっている。 言い換えれば、すでにある100V電圧を、アダプターを通して5Vに変換して利用できるようにするということだ。

利用する側はClientと呼び、この場合だと、人間そのものになるだろう。そして、人間はスマホを始めとした機器をつかう。これらの機器はClientである人間に対して、Targetと呼ばれる。Targetは、すでに家庭に供給されている100V電源をなんとかして使いたい。そこでAdapterの登場だ。ここではUSBアダプターがまさにAdapter役だ。また、すでに提供されている100V電源のようなものをAdapteeと呼ぶ。

クラスによるAdapter パターン

Adapterパターンを、クラスの関係に当てはめてみよう。

Adapteeは既に提供されているクラスとすることができる。 TargetはAdapteeを、なんとかして利用したいと思っているクラスだ。 その仲介役を担うクラスが、Adapterになる。 ClientはMainだったり、Activityだったり、Targetを呼び出すクラスとなるだろう。

「ドラえもん」で、Adapter パターンの実践

先程のクラスの関係をもとに、Adapterパターンを実践してみよう。

Adaptee役にDoraemonクラスを作ってみた。既に提供されているものとして扱うことにする。つまり、Doraemonクラスは固定されたものとして扱うのだ。

open class Doraemon {
    
    fun fetchTakeKopter():String {
        return "タケコプター!!!🚁"
    }

    fun fetchDokodemoDoor():String {
        return "どこでもドア!!!🚪"
    }
}

Doraemonクラスのメソッドをなんとか呼び出したいTargetが、つぎのHumanだ。ここではインタフェースとして、次のようなメソッドを定義している。

interface Human {
    fun wannaFly()
    fun wannaGoAnywhere()
}

当然このままではHumanとDoraemonは互換性がない。

そこでAdapter役として互換性を持たせるために、Requesterクラスを作ってみよう。このクラスはDoraemonクラスを継承し、Humanインタフェースを実装している。

class Requester: Doraemon(), Human {
    override fun wannaFly() {
        println(fetchTakeKopter())
    }

    override fun wannaGoAnywhere() {
        println(fetchDokodemoDoor())
    }
}

実装は単純で、Doraemonクラスから得られた文字列を、println関数でそのまま標準出力するだけである。

それでは、最後にmain関数を実装し、Client側でこれらを扱ってみよう。

fun main() {
    val nobita:Human = Requester()
    nobita.wannaFly()
    nobita.wannaGoAnywhere()
}

実行結果はこの様になる。

タケコプター!!!🚁
どこでもドア!!!🚪

main関数の中で注目すべきところは次の部分だ。

val nobita:Human = Requester()

nobitaオブジェクトはあくまでHumanインタフェース型であって、Requester型ではないというところだ。このことは、Doraemonクラスに存在する、fetchTakeKopterfetchDokodemoDoorなどの実装をClientに隠すことができる。

また、既存クラスの上に一枚クラスをかぶせるのでWrapperパターンとも呼ばれるようだ。

なぜAdapter パターンなのか

Adapterなんてややこしいものを作らず、直接Doraemonインスタンスを生成してメソッドを呼び出せば良いのでは?私も最初はそう思った。

Humanクラスを作成して、そこにDoraemonクラスをもたせることになるだろうか。しかしその場合、HumanクラスはいちいちDoraemonクラスのメソッドを知っていなければならない。それに、機能を変更したい場合に、DoraemonクラスとHumanクラスとの線引が不明確にならないだろうか?

一方、Adapterパターンを使えば、それらは一目瞭然。DoraemonクラスとHumanクラスが互いに関係しているのは、Requestクラスのみとなる。今後、Adapterを入れ替えたり、機能追加することも簡単だ。

つまりはAdapterパターンを使うということで、メンテナンスがしやすくなったり、クラスの再利用が可能になったりするのだ。Adapterパターンは既存のクラスを全く変えることがなく、目的のインタフェース(API)に合わせることができるために生まれるメリットがあることを覚えておこう。

ところでストーリー的に考えてみても、のび太がドラえもんの道具を直接、取り出すことができてしまうのはおかしいことなので、Adapterパターンによる呼び出しがしっくりくるのではないだろうか。

最後にひとつ。実は、Adapterパターンには二種類存在する。ひとつは継承によるパターン。もう一つは委譲によるパターンがある。今回の場合は前者の継承によるAdapterパターンを使ったことになることを付け加えておきたい。

今回のサンプルはGitHubからダウンロードできる。 https://github.com/araemon/AndroidExercise/tree/master/TryAdapterPatern

参考