2013年5月17日金曜日

design patterns – Adapter


アダプター

アダプターパターンでは、ニーズに合うようにインターフェイスを変換(適応)させます。それには、必要なインターフェイスを備えたオブジェクトを別途作成し、そのオブジェクトを、インターフェイスを変更したいオブジェクトに接続します。
図1.アダプターパターンの構造
図1.アダプターパターンの構造

アダプターを必要とする理由

非常によくあることですが、アプリケーションの開発や保守を行っていて、アプリケーションのチャンク(例えばログやその種のデータの保管に使用しているライブラリ)を置き換える必要があると判断したとします。新しく置き換えるために用意したライブラリが、古いライブラリとまったく同じインターフェイスを持っている可能性は低いでしょう。この先の作業については、次の2つの選択肢があります。
  1. コード全体を確認し、古いライブラリを参照している部分をすべて変更する
  2. 新しいライブラリで古いライブラリとまったく同じインターフェイスを使用できるようにアダプターを作成する
アプリケーションが小さかったり、古いライブラリの参照が少ししかない場合は、新しい抽象化層でコードをコンパイルするよりも、コード全体を見直して新しいライブラリに合うようにコードを変更した方がよいと思えることもあるでしょう。とはいえ、多くの場合は、アダプターを作成した方が現実的で時間の節約になります。

アダプターの例

それでは、このコードサンプルに、上述の仮想のロガーシナリオを適用してみましょう。元の自分のコードで、ログの生成用にブラウザーに組み込まれているコンソールを使用しているかもしれませんが、それには若干問題があります。コンソールはすべてのブラウザーに組み込まれているわけではないので(特に古いブラウザーにはないことが多い)、自分のアプリケーションを他のユーザーが使用した場合に生成されるログを見ることができず、自己テストで検出できなかった問題を調べられないのです。そこで、ロガーを導入し、AJAXを使用してそれらのログをサーバーに転送して処理してもらうことにしました。新しいAjaxLoggerライブラリのAPIは以下のようになります。
AjaxLogger.sendLog(arguments); AjaxLogger.sendInfo(arguments); AjaxLogger.sendDebug(arguments); etc...
見たところ、このライブラリの作成者は、開発者がコンソールの置き換えにこのコードを使用することを想定していなかったため、各メソッド名の先頭に「send」を付ける必要があると考えたようです。そこで、「ライブラリを編集して、メソッド名を変更すれば済む話ではないか?」という疑問が出てきます。しかし、次の2つの理由により、その方法はお勧めしません。ライブラリをアップデートする必要が出てきたときに変更が上書きされてしまうので、開発者は再び名前変更の作業をやり直すことになります。また、ライブラリをコンテンツデリバリーネットワークからプルダウンする場合に名前を編集できないからです。そこで、新しいライブラリをコンソールと同じインターフェイスに適応させるオブジェクトを作成することにしましょう。
var AjaxLoggerAdapter = { log: function() { AjaxLogger.sendLog(arguments); }, info: function() { AjaxLogger.sendInfo(arguments); }, debug: function() { AjaxLogger.sendDebug(arguments); }, ... };

使い方

コンソールを使用する開発者はたいてい、参照によって直接コンソールを呼び出すものです。そこで、console.xxxの呼び出しごとに、コンソールではなく、新しいアダプターを参照するにはどうすればよいでしょう。ファクトリのような抽象化を使用していた場合、抽象化層に変更を加えるだけで済みますが、上で述べたとおり、皆が単にconsoleを直接参照しているのです。さて、JavaScriptは動的な言語であり、実行時に変更を行うことができます。そこで、新しいAjaxLoggerAdapterでコンソールをオーバーライドしてみたらどうでしょうか。
window.console = AjaxLoggerAdapter;
このやり方は簡単ですが、それだけに注意が必要です。他の人によって使用される前提のコードに対してこのようにすると、コンソールは、それらのユーザーの期待どおりには機能しなくなります。また、このコード例の単純さにだまされないでください。多くの場合、メソッドが簡単に対応し合う(sendLoglogのように)ことはありません。新しいライブラリと互換性を保つようにインターフェイスを変換するには、実際に自分のロジックを多少実装する必要があるかもしれません。

0 件のコメント: