NullObjectパターン
デザインパターンの必要性
デザインパターンは、GoF(*1)の「オブジェクト指向における再利用のためのデザインパターン」(1995年、日本語訳1999年。以下、GoF本)によって紹介された、23のパターンによって広く知られるようになりました。10年以上がたった今でも多くの設計でこのGoF本で紹介されているデザインパターンが利用されています。
デザインパターンは、オブジェクト指向において普遍的なテクニックを示したものであり、デザインパターンを知ることによって、再利用性の高い「良い」設計を行うことができます。デザインパターンは、ある問題領域に対しての解決策を示しています。つまり、ある問題に対して、どう設計したらよいかというヒントを教えてくれる指南役なのです。
長年設計を行っている人は、自然とこうしたパターンを身につけて利用しているかも知れません。しかし、デザインパターンでは、そのパターンの目的と動機、適用可能範囲を示すことで設計のためのノウハウが整理されています。デザインパターンとして理解しておくことで、次に同じような問題が発生したときに、すぐにそれを応用できます。また、比較的経験が短くても、先人たちのよりよい設計を短時間で理解し、応用できるようになります。デザインパターンはベテランだけでなく、初中級者にとっても知っておくことで、メリットがあると言えます。
しかし、GoF本の23のデザインパターンはあまりにも有名で、すでにさまざまなところで解説されています。そこで、この連載では、GoF本で紹介されている以外のデザインパターンで有用なパターンに焦点を当てて説明していきたいと思います。
(*1):Gang of Four デザインパターンの本における4人の著者(エリック ガンマ、ラルフ ジョンソン、リチャード ヘルム、ジョン ブリシディース)を表す。
デザインパターンとフレームワーク
ソフトウエアのフレームワークは、アプリケーションのための再利用可能な構造や仕組みを提供するクラス群であると言えます。再利用という点では、デザインパターンと目的を同じにしていますが、デザインパターンとフレームワークには以下のような違いがあります。
1.デザインパターンはフレームワークより抽象的である。
フレームワークは、コードで表現されていますが、デザインパターンは、設計のパターンを示したものであり、より抽象的なものです。デザインパターンはその一例だけをコードで表現できます。
2.デザインパターンはフレームワークより細かな要素である。
フレームワークには、デザインパターンを含んでいますが、その逆はあり得ません。
3.デザインパターンはフレームワークほど分野に特化していない。
フレームワークは特定のタイプのアプリケーションに特化しています。例えば、Webアプリケーションのフレームワークは、Webアプリケーション以外には適用できません。デザインパターンは、その目的と適用可能範囲が合えばどのようなアプリケーションにも適用可能です。
ここで示したようにフレームワークとデザインパターンは別のものです。しかし、一方でフレームワークの中ではデザインパターンが利用されているのです。フレームワークでは、デザインパターンが具体的な実装として利用されており、デザインパターンの目的や価値を知る上で非常に良い題材であると言えるのです。
GoF以外のデザインパターン
GoFの示した23のデザインパターンは、目的別に生成、構造、振る舞い、適用範囲として、クラス、オブジェクトに分類されて整理されています。それぞれのデザインパターンは基本的なパターンを示しており、実際は、ある1つのパターンを利用するだけでなく、複数のパターンを組み合わせて協調して利用されることが多いです。
GoF以外のデザインパターンも同様に、生成、構造、振る舞いの目的別に分類することができます。これらに加えて、マルチスレッドのためのデザインパターンがあります。GoFのデザインパターンは主にsmalltalk時代の実装で使用されてきたパターンを整理したものです。このGoFのパターンは、JavaやC#の場合でも有効です。
しかし、JavaやC#のような新しい言語、環境に応じてほかのパターンが有効だったり、新たなパターンが必要になったりした結果、GoF以外のデザインパターンも語られるようになってきたということもできます。
実際、GoF以外のデザインパターンは、JavaやC#のライブラリやフレームワークなどに使用されているものが多いと言えます。また、JavaやC#の環境では、Webアプリケーションに代表される、マルチスレッドのプログラムを記述する機会が多く、マルチスレッドに対するパターンも多く登場してきたと言えます。
NullObjectパターン
最初に紹介するのは、NullObjectパターンです(図2)。NullObjectとは、何もしないオブジェクトのことです。何もしないオブジェクトに意味があるのかと思いますが、使い道はいくつかあります。
1つは、JavaやC#において、オブジェクトの参照が存在しない状態(null)の代わりにNullObjectを定義して利用するというパターンです。例えば、メソッドの戻り値やフィールドの値がnullになる可能性がある場合、NullPointerException(javaの場合)やSystem.NullReferenceException(C#の場合)などのエラーが実行時に起こる可能性があります。しかし、nullの変わりに何もしない、NullObjectを定義して用いれば、nullによるエラーが起きる可能性をなくすことができます。
NullObjectの使い道はほかにもあります。例えば、テスト用に取りあえず動かしたいとか、設定がない場合には何もしないといった場合にNullObjectを使うことによって何の動作もしない状態を実現します。
NullObjectパターンを実装する場合には、Singletonパターンと組み合わせて用いられることが多いです。NullObject自身は何もしないので、状態の違いはなくインスタンスは1つあれば十分です。そのため、NullObjectをSingletonとして実装して、インスタンスを1つに限定します。
NullObjectパターンはStrategyパターンと組み合わされることもあります。Strategyパターンは、戦略を切り替える、つまりオブジェクトの振る舞いを切り替えたい場合に用いられますが、その要素の1つとして何もしないNullObjectを用意するといった使い方がされるからです。
Log4Jに見るNullObjectパターン
NullObjectパターンが実装されている例として、Log4Jの中のNullAppenderがあります。Log4Jは有名なロギングのフレームワークで、ログの出力方法を切り替えるためにAppenderクラスを用います。
Log4Jでは、Appenderインターフェースを実装した、AppenderSkeletonというアブストラクトクラスがあり、各Appenderクラスは、AppenderSkeletonを継承して作られています。例えば、WriterAppenderは、ストリーム系へ出力するためのAppenderクラスで、AppenderSkeletonを継承しています。これは、ログを出力する処理を切り替えるためにStrategyパターンが適用されていると言えます。
同様に、NullAppenderもAppenderSkeletonを継承していますが、動作に必要なactivateOptions、addFilterなどのメソッドの中では何もしていません。結果として、NullAppenderを使った場合はログを出さないということになります。例えば、ログを出したくない場合に、設定ファイルがないと警告が出ますが、NullAppenderを設定しておけば警告なしでログを出さない状態にできます(*2)。
(*2)しかし、実際はNullAppenderが利用されることはあまりなかったようで、Log4J1.3からはNullAppenderは削除されています。
このようにNullObjectパターンは、何もしないケースをほかのケースと同様に扱えるというメリットがあります。
パターンの組み合わせ
NullAppenderの実装を見てみると、getInstance()メソッドでは、staticなフィールドinstanceにセットされたインスタンスを返すようになっており、Singletonパターンになっています。NullAppenderは状態を持つ必要がないので、余分なインスタンスを作らないようにしているわけです。
ここまで、Log4Jというロギングフレームワークの中で、NullObjectパターンとそのほかの複数のパターンが組み合わされて使われている例を見ていきました。最初に説明したように、フレームワークの中にはデザインパターンが含まれているということがわかると思います。
【参考文献】
Erich Gamma(原著), Ralph Johnson(原著), Richard Helm(原著), John Vlissides(原著), 本位田 真一(翻訳), 吉田 和樹(翻訳)
「オブジェクト指向における再利用のためのデザインパターン」改訂版 ソフトバンククリエイティブ(発行年:1999)
Mark Grand(著),原 潔(翻訳),瀬尾 明志(翻訳),宮本 道夫(翻訳)「UMLを使ったJavaデザインパターン―再利用可能なプログラミング設計集」カットシステム(発行年:2000)