ARCHIVES
アーカイブス
アーカイブス

Java開発者必修! ソフトウェア・パターン専科 ソフトウェア・パターンへのいざない

IDGジャパン JavaWorld誌 2003年2月号より連載中の「ソフトウェア・パターン専科」の原稿を元に再構成

高木 大輔・石川 智久・吉田 悦万
2003年02月01日
※内容は公開当時のものです

はじめに

ソフトウェア・パターンという言葉を聞いたことがあるだろうか。ソフトウェア・パターンの中ではデザイン・パターンが有名であり、すでに実際の開発現場で適用されている方も多いと思われる。

デザイン・パターン以外にも多くのソフトウェア・パターンが存在しているのだが、残念ながら、デザイン・パターン以外のパターンについてはあまりよく知らないという方が多いのではないだろうか。 ソフトウェア・パターンに関する知識は、我々ソフトウェア開発者にとって、今後ますます重要になってゆくであろう。

本連載では、ソフトウェア・パターンについて学びたい読者や、デザイン・パターンは知っているけれど他のパターンについてはあまり知らないという読者を対象に、さまざまなソフトウェア・パターンについて解説していく予定だ。

ソフトウェア・パターンとは

そもそも、ソフトウェア・パターンとは何だろうか。ひと言でいえば、ソフトウェア・パターンとはソフトウェア開発の各局面において繰り返し現れる問題とその解決方法をあらわしたものといえる。

問題と解決方法をパターンとして記述し、それを再利用しようという試みは、元々、建築の分野で始まった。1970年代、著名な建築家であるChristopher Alexander氏は、建築における253のパターンを識別し、それを再利用することによって、少ないコストで誤りの少ない建築設計を行うことを提唱した(コラム「Alexanderのパターン」参照)。この考えをソフトウェア開発に適用したものが「ソフトウェア・パターン」である。

さまざまなパターン

冒頭で述べたように、ソフトウェア・パターンにはデザイン・パターン以外にも多くの種類が存在する。それでは、数あるソフトウェア・パターンのうち、代表的なものについて簡単に紹介してゆこう。

デザイン・パターン

デザイン・パターン(設計パターンとも呼ばれる)は、ソフトウェアの設計段階におけるパターンである。

デザイン・パターンは、様々なWebサイトや書籍等で活発な議論が行われ、多くのパターンが提案されているが、この分野における最も著名な書籍として、Erich Gamma、Richard Helm、Ralph Johnson、John Vlissidesの4人組(通称『Gang of Four:GoF』)による『Design Patterns: Elements of Reusable Object-Oriented Software』(邦訳『デザイン・パターン: オブジェクト指向における再利用のための』 通称『GoF本』)があげられる。

同書籍は『アナリシスパターン』『リファクタリング』などの著者であり、XP(eXtreme Programming)においても著名な、Martin Fowler氏が「過去10年間ソフトウェアに関して書かれた本の中でこれほど重要な本はない」と称するほど、その後のパターンムーブメントに多大な影響を与えた書籍である。

年月の経過による難点も多少ある(サンプルソースがC++とSmalltalk、表記法がUML登場以前のOMT法)ものの、ソフトウェア・パターンを語るうえで必読書といっても過言ではないといえる。

GoF本では、パターンを「生成に関するパターン」「構造に関するパターン」「振る舞いに関するパターン」に分類し、Abstract Factory、Singleton、Composite、Observer等合計23のパターンを定義している。

J2EEパターン

分類としてはデザイン・パターン、すなわち設計局面で適用すべきパターンであるといえるが、J2EEプラットフォームを前提としている点が、J2EEパターンの特徴といえる。

元々はWebサイト上で公開されていたもの( http://developer.java.sun.com/developer/technicalArticles/J2EE/patterns/ )を、より詳細化した書籍『Core J2EE Patterns: Best Practice And Design Strategies』(邦訳『J2EEパターン:明暗を分ける設計の戦略』)が出版されている。

J2EEプラットフォームを「クライアント層」「プレゼンテーション層」「ビジネス層」「インテグレーション層」「リソース層」という論理的な5層モデルとして捉え、このうちの「プレゼンテーション層」「ビジネス層」「インテグレーション層」をJ2EEパターンの対象として、「Front Controller」「Business Delegate」「Data Access Object」といった合計15のパターンを提案している。

アナリシスパターン

アナリシスパターン(分析パターンとも呼ばれる)は分析段階におけるモデリング作業で役立つパターンである。

この分野における代表的な書籍としては、先にあげたMartin Fowler氏の『Analysis Patterns: Reusable Object Models』(邦訳:『アナリシスパターン: 再利用可能なオブジェクトモデル』)が有名だ。

同書籍は他のパターンカタログとは異なり、特定のテンプレートを用いずにパターンを解説している。また、ある文脈に関するモデルを様々な抽象度・視点からインクリメンタルに表現し、モデルを進化させてゆく過程が描かれているアプローチも特徴であるといえる。

これは、各パターンが単独で役立つものではなく、互いに影響し合うことを強調するため、意図的にこのようなアプローチをとっていると思われ、本文中に「各パターンは、同じ対象の問題領域内で作業をする人々にとって明らかに有用であるが、その基本のパターンは、他の問題領域においても有用なはずである」ともある。

同書籍ではアナリシスパターンの例として「責任関係」「観測」「範囲」「勘定」「会計」等をあげている。

アンチ・パターン

デザイン・パターン等のパターンが「成功のための定石」であるのに対し、アンチ・パターンは「成功するために避けるべきこと」であるといえる。 アンチ・パターンのカタログの代表例に『Anti Pattern: Refactoring Software, Architectures, and Projects in Crisis』(邦訳『アンチ・パターン: ソフトウェア危篤患者の救出』)があげられる。 同書籍では、まずアンチ・パターンに陥る根本的なよくある原因について述べ、そして、アンチ・パターンに陥ってしまったときの再構想による解決法を述べている。 つまり、アンチ・パターンに陥らないためにその原因を示唆することで予防策を立てる手助けをし、それでも陥ってしまったとき、あるいは問題点を分析した結果、現状にあてはまってしまっているアンチ・パターンを「どうやって対処すべきか?」という両面について言及しているといえる。(図1)

同書籍ではアンチ・パターンを「開発の視点」「アーキテクチャの視点」「プロジェクト管理の視点」に分類し、「肥満児」「機能的分解」「システムのおんぼろ煙突化」「横紙破り」等のパターンをあげている。

図1

ソフトウェア・パターンの利点

個々までの記述から、いろいろな種類のパターンが存在することがおわかりいただけたのではないだろうか。ところで、ソフトウェア開発においてソフトウェア・パターンを使用する利点とは何だろうか。それは以下の2点があげられるだろう。

実証済みの解決策を再利用する

ソフトウェア・パターンの第一の利点は、先人によって既に効果が実証されている解決策、すなわちソフトウェア開発における定石を利用できることにある。 ソフトウェア・パターンの知識を身に付けることにより、すでに知られている問題に陥らずに済むのである。ソフトウェア・パターンの多くはカタログ形式で提供されており、ソフトウェアの開発者が問題に直面した際に、その問題に対するソフトウェア・パターンをすばやく探すことができるだろう。

ちなみに、コンポーネントやフレームワークも再利用を目的としているが、これらの2つとソフトウェア・パターンの違いは何だろうか。それは、コンポーネントやフレームワークがソフトウェア自体(プログラムソースやバイナリモジュール)の再利用を目的としているのに対し、ソフトウェア・パターンは知識や概念の再利用を目的としている点といえるだろう。

共通用語を提供する

ソフトウェア・パターンのもう一つの利点は、開発者に「共通用語」を提供し、開発者間のコミュニケーションを円滑にすることである。

ソフトウェア・パターンが出現する以前にも、「クイックソート」「二分探索」や「スタック」「キュー」など、アルゴリズムやデータ構造に関する共通の用語は存在していた。しかし、それよりも抽象度の高い設計や分析に関する共通の用語はほとんど存在しなかったのである。

抽象度の高い概念を正しく簡潔に伝えるのはとても難しいことといえる。例えば、開発者が自らの設計を他の開発者に伝える際には、その構造や意図を長々と説明する必要があったのではないだろうか。ソフトウェア・パターンの出現によってそれらの概念に名前が付けられ、単に「○○パターンを使っている」と述べるだけで済むようになるのである。

デザイン・パターン

早速、連載の第一回としてデザイン・パターンを取り上げよう。ただし、デザイン・パターンの個々のパターンに関する解説は次回以降に譲り、今回はデザイン・パターンの概要や、デザイン・パターンを適用する際の注意点について解説してゆこう。

良い設計とは

さて、ソフトウェアの開発時にデザイン・パターンを利用する目的は何だろうか。もちろん良い設計(デザイン)を行うためだろう。それでは果して、良い設計とは何だろうか。

Robert C. Martin氏は、良いオブジェクト指向設計が満たすべき11の法則を集め、Object Oriented Design Principlesとして発表した。代表的なものを以下に紹介しよう。

The Single Resddonsibility Princiddle (SRP)
1つのクラスに複数の責務を与えてはいけない
The Liskov Substitution Princiddle (LSP)
サブクラスは基底クラスの代わりとして振舞えなければならない
The Deddendency Inversion Princiddle (DIP)
抽象に依存すること。具象に依存してはならない

これらのObject Oriented Design Principlesの中で、もっとも重要と考えられるThe Open-Closed Principleの観点からデザイン・パターンについて考えてみよう。

The Open-Closed Principle

The Open-Closed Principle(以下OCP)とは、「契約による設計(Design by Contract)」で有名な、オブジェクト指向言語Eiffelの考案者であるBertrand Meyer氏が提唱した以下の原則のことである。

ソフトウェアの構成要素(クラス、モジュール、関数など)は、拡張に対して開いていなければならないが(Open)、修正に対して閉じていなければならない(Closed)

ここで述べられている「拡張に対して開いている」「修正に対して閉じている」とはどういう意味だろうか。

「拡張に対して開いている」とは、そのモジュールが拡張可能であり、機能を追加できることを示している。一方、「修正に対して閉じている」とは、その修正に際し、既存のコードを修正する必要がないことを示している。つまり、OCPを満たしている設計では、既存のコードを修正せずに機能を追加できる、と述べているのである。

確かに、このような条件を満たしている設計は良い設計と言えそうだ。では、具体的にどのような設計を行えばOCPを満たすことができるのだろうか。

例として、アプリケーションのログを出力するためのクラスについて考えてみよう(図2)。

図2

このログ出力クラスには、String型の文字列を受け取るdebugメソッドおよびerrorメソッドが定義されており、それぞれ、受け取った文字列をデバッグメッセージおよびエラーメッセージとして標準出力に出力する(リスト1)。

リスト1:ログ出力クラス(Logger.java)

public class Logger {
  public void debug(String message) {
    System.out.println("DEBUG: " + message);
  }

  public void error(String message) {
    System.out.println("ERROR: " + message);
  }
}

さて、このクラスはログを標準出力に出力するようになっているが、将来、ログをファイルに出力する機能が必要になることが容易に想像できるだろう。例えば、開発時にはログの内容をコンソールに出力し、本番運用時にはファイルに出力したいなどということはよくあるのではないだろうか。そこで、ログの出力先を切り替える機能を追加することにしよう。

ところが、Loggerクラス内部で出力先が標準出力に固定されているため、この機能を追加するにはLoggerクラス自体を修正しなければならない。つまり、Loggerクラスは「ログの出力先を変更する」という点に関して、OCPを満たしていないということになるのである(修正に対して閉じていない) 。それでは、Loggerクラスの設計をどのように修正したらよいだろうか。

継承を用いる方法

Loggerクラスを抽象クラスに変更し、そのLoggerクラスを継承して、ログを標準出力に出力するクラス(ConsoleLogger)およびファイルに出力するクラス(FileLogger)をそれぞれ作成することにしよう(図3)。

図3

この場合、ログを標準出力に出力するときはConsoleLoggerクラスのインスタンスを生成して使用し、ファイルに出力するときはFileLoggerクラスのインスタンスを生成して使用するだけで済む。今までLoggerクラスを使用していたクライアントは、実際のインスタンスがどのクラスのインスタンスであるか意識する必要はなくなる。新たに別の出力先にログを出力したいときでも(例えばデータベースなど)、対応するLoggerクラスのサブクラスを追加するだけでよく、Loggerクラス自体や他のLoggerクラスを修正する必要はないのだ。つまり、この設計は「ログの出力先を変更する」という点に関して、OCPを満たしているということになる。

ところで、修正後のLoggerクラスは「ログの出力先を変更する」という点に関してはOCPを満たしているが、例えば「ログのフォーマットを変更する」という点に関しては、OCPを満たしていない。現状では、メッセージの先頭に"DEBUG:"、"ERROR:"という文字列を付加するだけだが、ログの出力日時も付加したくなるかもしれない。このようなとき、同じように継承を用いて拡張すると、「"DEBUG:"、"ERROR:"を付加して標準出力に出力するクラス」「ログの出力日時を付加して標準出力に出力するクラス」「"DEBUG:"、"ERROR:"を付加してファイルに出力するクラス」「ログの出力日時を付加してファイルに出力するクラス」等々、Loggerクラスのサブクラスが爆発的に増加してしまうことが予想できるだろう。

では、ログの出力先を変更できるようにしつつ、フォーマットも変更できるようにするだめには、どのような設計にする必要があるのだろうか。

委譲を用いる方法

Loggerクラスをそのまま継承して個別の処理を実装するのではなく、Loggerクラスからメッセージを出力するためのインターフェース(LogWriter)を導出し、そのインターフェースを実装して、標準出力に出力するクラス(ConsoleLogWriter)およびファイルに出力するクラス(FileLogWriter)を作成する。同様にメッセージをフォーマットするためのインターフェース(LogFormat)を導出し、"DEBUG:"、"ERROR:"の文字を付加するだけのクラス(SimpleLogFormat)およびログの出力日時を付加するクラス(DateLogFormat)を作成することとする。

そして、Loggerクラスはメッセージのフォーマットや出力の処理を、それぞれLogWriterやLogFormatに委譲するのだ。(図4) 。

この場合、ログの出力先を切り替えるには、対応するLogWriterのインスタンスを指定すればよく、Loggerクラスを利用するクライアントを修正する必要はなくなる。また、Loggerクラスや他のLogWriterクラスを修正する必要もない。

同様に、ログのフォーマットを変更するときも、対応するLogFormatのインスタンスを指定すればよく、クライアントやLoggerクラス、他のLogFormatクラスを修正する必要はない。つまり、修正後の設計は、「ログの出力先を変更する」と「ログのフォーマットを変更する」という2つの点に関してOCPを満たしているといえる。

さて、今までの議論がデザイン・パターンとどのように関係するのか疑問に思った方もおられるかと思う。今回の例では、OCPを満たすように最初の設計を進化させていったわけだが、実は、この最終的な形はデザイン・パターンの一つである「Strategyパターン」と呼ばれるものなのである(図5)。

デザイン・パターンにはOCPを満たす設計が多くあり、少々乱暴な言い方をすれば、デザイン・パターンとは、OCPを満たすような設計の集まりと言えるだろう。

デザイン・パターンを適用する際には、個々のデザイン・パターンがOCPの「どのような拡張に対して開いているのか」、「どのような修正に対して閉じているのか」を意識することが大切といえるのである。

図4

図5

参考までに、文献(下記参照)より「変更から見たパターンの例」を引用しよう (表1)。

表1:変更から見たパターンの例

変更箇所(拡張可能な点) パターン
アルゴリズム Strategy、Visitor
オブジェクトの状態 State
オブジェクトの振る舞い Decorato
インターフェース Adapter
実装方法 Bridge
オブジェクト間の通信方法 Mediator
コンテナクラスのアクセス方法 Iterator

参考文献

「デザイン・パターンとOpen-Closed Principle」石井勝氏
http://member.nifty.ne.jp/masarl/article/dp-ocp.html
「The Open-Closed Principle」Bertrand Meyer氏
http://www.objectmentor.com/resources/articles/ocp.pdf
「Object Oriented Design Principles」Robert C. Martin氏
http://www.objectmentor.com/mentoring/OOPrinciples
「オブジェクト指向における再利用のためのデザイン・パターン」 (著者:Erich Gamma氏、Richard Helm氏、Ralph Johnson氏、John Vlissides氏/監訳:本位田真一氏、吉田和樹氏/発行:SOFTBANK BOOKS,1995)
「J2EEパターン ー 明暗を分ける設計の戦略」 (著者:ディーパック・アラー氏、ジョン・クルーピ氏、ダン・マークス氏/監訳:ウルシステムズ株式会社/発行:ピアソン・エデュケーション,2002)
「ソフトウェア・パターン再考 パターン発祥から今後の展望まで」 (著者:鈴木純一氏、長瀬嘉秀氏、田中祐氏、松田亮一氏/発行:日科技連, 2000)
「オブジェクト指向トラック パターンとフレームワーク」 (著者:Ralph E. Johnson氏、中村弘明氏、中山裕子氏、吉田和樹氏/発行::共立出版,1999)

アーカイブス一覧へ