Skip to content.

Sections
Personal tools
You are here: Home » 技術文書 » パターン » Java プログラマのためのデザインパターン入門

Document Actions

Javaプログラマのためのデザインパターン入門

(株)永和システムマネジメント   平鍋健児・山田健志
作成日:

この記事では、ソフトウェアパターンの中でも、特に Gamma らの著書「デザインパターン」に絞って入門者および中級者向けの解説を行う。 Java プログラミングの経験はあるがデザインパターンはよく知らない、 あるいは、 よく知っているが、実際の開発で活用するにはどうしたらよいか悩んでいる という読者を対象としている。 まず、なぜデザインパターンが重要かということを述べた後、 書籍「デザインパターン」の読み方を解説する。 さらに、パターンの持つ特質である生成性を述べ、 最後に、実際に動作する Java アプリケーションをデザインパターンを利用しながら開発する例を説明する。

デザインパターンの効用

「デザインパターン」は Gamma らの著書によってソフトウェア設計における良質なデザインテンプレート集として広く認知されているが,実際の開発現場では,どの程度普及したであろうか.もし読者が java プログラマであり,まだ実際にデザインパターンを活用したことがないとしたら,それは多くのチャンスを逃していると思う.デザインパターンを知り,活用することで,以下のような豊富なメリットが得られる.


  自分で設計を再発明するより少ない労力で,良い設計ができる"

例えば,C 言語の経験があるプログラマであれば,「線形リスト」の構造体とそれを操作するアルゴリズムを,これまではたして何回コーディングしたかを思い出してみれば良い.繰り返し現われるプログラムを再利用する方法の1つとして,ライブラリを作成する,という方法がある.「線形リスト」はライブラリのよい候補であり,java 言語やC++言語では標準ライブラリに取り入れられている.デザインパターンはこれより少し抽象度が高く,設計の再利用を目指している.中級以上のオブジェクト指向プログラマなら,書籍「デザインパターン」を読むと強烈なデジャヴ感覚に襲われるはずだ.そこにはあなたがかつて苦心して設計した「自分だけの定番クラス」に非常に良く似た設計が,名前を持って登場しているからだ.


  Java のライブラリをすっきり理解できる

Java2 SDK1.2 のコアライブラリには,デザインパターンがふんだんに取り入れられている.SDK のソースコードを読むことは,Java とデザインパターンの両方を理解する一石二鳥のよい材料となる.例えば,java.io.OutputStream の Decorator パターン,java.awt.LayoutManager のStrategy パターン,などはデザインパターンを取り入れた設計のお手本になっている.是非一度はソースコードを追いかけてみて欲しい.


  チームメンバに少ない単語で正確な設計を伝える

デザインパターンを設計カタログとして見ることができる.設計カタログには名前がついており,その名前をチームメンバ全員が知っている場合,自分が思いついた設計をメンバに伝えるときに便利だ。

「データベース変更時に,画面を変更するには,Observer パターンを使おう」

とか,

「この通信プロトコルの状態遷移を管理するには,State パターンを使おう」

などという会話ができる.これは設計者間のコミュニケーションの帯域を大きく減らす可能性がある.少ない単語で,多くのことを正確に伝えることができるのだ.これは,ちょうどバレーボールのセッターが,「Aクイック」というサインを出すのに似ている.メンバの全員がそのパターンが意味することを知っており,瞬時にその構造と振る舞いを思い起こすことができる。

書籍「デザインパターン」を過少評価している人の,パターンに対するの正当でない批判の1つに,「それらのパターンは私も以前から知っており,使っていた」というのがある.書籍「デザインパターン」は,1つ1つのパターンが優れているかどうかという点が評価の観点ではなく,それらに名前を付け,カタログ化してプログラマの共有知識にまで仕上げた点が評価されるべきである。


  美しい設計を見て感動できる

最後に,「デザインパターン」には「なるほど!」とか「美しい!」という感動が詰まっている.そこには,多くの設計上の工夫と,それを行うことによるトレードオフ,過去のプロジェクトで得られた経験が,抽象度の高い言葉で結晶している.ある意味で美が表出しており,鑑賞に値する. デザインパターンの起源が建築にあり,機能を表現していくと構造に美が現われる, という意味がよく分かる.


書籍デザインパターンの読み方

  パターンの記述

もし読者が,パターンは「解決法」である,と思っているのならばそれは間違いである.まず,パターンは「解決法」を書いているのではない.パターンは,ある「文脈」における「問題」と「解決」の組に名前を付けたものである.

以下に,書籍「デザインパターン」が各パターンを記述するのに使用している フォーマットを挙げる.

  • 名前と分類 ... 文字通りパターンの名前.および後述するカテゴリ
  • 目的 ... ここに,解こうとしてる「問題」と「解決」の原理が書かれる
  • 別名 ... 良く知られた別名が紹介される
  • 動機 ... どのような状況で問題が発生するかが,具体例あるいはストーリとして書かれる
  • 適用可能性 ... どのような状況で適用できるかが書かれる
  • 構造 ... パターンの構造を示すクラス図
  • 構成要素 ... クラス図中のクラス・オブジェクトの責任分担
  • 強調関係 ... 構成要素がどのように強調するか
  • 結果 ... パターンが問題の解決にどう貢献するか.また,トレードオフなど
  • 実装 ... 実装に移す場合の注意点
  • サンプルコード ... コードが例示される
  • 使用例 ... 実際のシステムで利用されている例
  • 関連するパターン ... 他の密接に関連するパターン

特に,パターンを理解するためには,最初にまず「目的」と「動機」を読む.そして,「問題」が何であり,それがどういう「文脈」で発生するかを把握することが先決である.「目的」には,「問題」が書かれることが多い.例えば,Singleton パターンの「目的」は,こうである.

あるクラスに対してインスタンスが1つしか存在しないことを保証し,それにアクセスするためのグローバルな方法を提供する.

ここには,「クラスに対してインスタンスが1つしか存在しないことを保証するには, どうしたらよいか」という問題が書かれている. しかし,このような問題の記述は抽象的であることが多く, パターンによっては理解するのが難しい場合もある.「問題」の理解を助けるのが, 「動機」の記述である.「動機」は,実際にそのような問題が起こる状況, すなわち文脈が,具体的な例を伴って書かれている.

例えば,Singleton の 「動機」を読むと,そこにはそのような状況の例として,プリンタスプーラ,ファイルシステム, ウィンドウマネージャ,などの例が挙げられている.

デザインパターンを利用するには,まず「目的」と「動機」を読み, そのパターンが解こうとしている「問題」とそれが発生する「文脈」を理解する. そして,そのような状況に自分が置かれたときに, このパターンを思い出せるようにしておく必要がある.


  デザインパターンのカテゴリ

デザインパターンを活用できるように,分類して整理する方法がある. デザインパターンは23個あり,目的別に「構造」,「振る舞い」, 「生成」の3カテゴリに分類されている.また各々のパターンが,クラスに適用されるか, オブジェクトに適用されるかでさらに2つに細分される.クラスのパターンは, クラス間の関連を主に扱う.クラス間の関連である継承がコンパイル時に決定してしまうため, より静的なパターンとなる.これに対してオブジェクトのパターンは, オブジェクト間の関連を主に扱う. これは実行時に変更可能であり,より柔軟で動的なパターンとなる.

各パターンの目的を,このように分類して覚えておくこともできる.

 生成構造振る舞い
クラス FactoryMethod Adapter (Class) Interpreter
TemplateMethod
オブジェクト AbstractFactory
Builder
Prototype Singleton
Adapter(Object)
Bridge
Composite
Decorator
Facade
Flyweight
Proxy
Chainof Responsibility
Command
Iterator
Mediator
Memento
Observer
State
Strategy
Visitor

パターンの生成性

パターンは,1つのみ孤立して使われるよりも複数組み合わせて効果を発揮する場合が多い.書籍「デザインパターン」にも,パターンの関連図が示され, 各パターンの周りにはどのような関連するパターンがあるかが図示されている.1つのパターンの周りには必ず関連するパターンがある.複数のパターンを組み合わせて設計することによって,問題に対する解を生成(generate)することができる.パターンのこの性質を生成性(generativity)という.「デザインパターン」から外れてしまうが,ソフトウェアパターンの中でも特に生成性に焦点をあてたパターン集が存在する.1つの題材に関連する複数の小さなパターンをまとめたものを「パターン言語」という.ちょうど,自然言語における単語が,パターン言語におけるパターンに相当する.

「デザインパターン」は生成性が強いパターン集ではないが,パターンの 生成性の例を,SDK の中に見ることができる.例えば,java.awt の設計の一 部を見てみる.そこでは,「様々なウィンドウシステム上に,同じルックアン ドフィールを持った抽象的で移植性の高いGUI部品ライブラリを構築するには どうしたらよいだろうか」という問題に対して,「抽象インターフェイスと ウィンドウシステム毎の実装を Bridge する.ウィンドウの生成には,ウィンドウシステム毎用 意された SingletonAbstractFactory を使う」という解が使われている.Singleton, AbstractFactory , Bridge という3つのパターンが有機的に結びついて1つの設計となっている良い例である.

  java.awt クラスの設計


awt
図 1

パッケージ名 役割
java.awt アプリケーションプログラマが直接利用するGUI部品クラス
および,それらを生成する抽象 Toolkit クラス
java.awt.peer ウィンドウシステム毎の Peer が共有するインターフェイス
sun.awt.motif Motif による Peer の実装
sun.awt.windows Windows による Peer の実装

この例では,Bridge を2段 に用いて,java.awt を利用するアプリケーションプログラマに裏で実装とし てどのウィンドウシステムを利用するかを隠し,GUI 部品の継承階層のみに注 目させている.GUI 部品の生成を,Toolkitという AbstractFactory を用いて局所化し,その実装もやはり ウィンドウシステム毎に用意する.Peer と Toolkit にも,抽象的なインター フェイスが提供されている.これらに対して,複数のウィンドウシステムによ る実装を別個のパッケージ内で提供することにより,実行時の環境によって実 装を選択することに成功している.Toolkit が Singleton になることにより,システム全体で現在動作 しているウィンドウシステムを1つに限定し,さらに,部品生成時の AbstractFactory へのグローバルなエントリポイント(プログラムで最初の参照を得るための 取っ掛かり)を与えている.

この設計にもデメリットは存在する.すなわち,GUI 部品の継承ツリーが, 並行して何本もできてしまうことである.すなわち,java.awt, java.awt.peer, および各実装(sun.awt.motif, sun.awt.windows, ...)に同じツリーが存在する. 1つ GUI 部品が追加されると,3本のツリーに同様の追加が発生する. これは,上記の利点とのトレードオフとなる.


デザインパターンによるJavaアプリケーションの開発事例

ここからは,実際のプログラム(簡単なファイルエクスプローラアプリケーション) の設計を行いながら,デザインパターンを解説する. ここで利用するデザインパタ ーンは,下記の6つであり,Multicast パターン以外は,書籍 「デザインパターン」 にも記されているパターンである

  • Composite パターン
  • Proxy パターン
  • Prototype パターン
  • Singleton パターン
  • Adapter パターン
  • Multicast パターン

これから設計を行う Java プログラムは図2のような,ファイル情報を表示する アプリケーションである. Windows を使っている読者はエクスプローラーを連想して 頂ければよい. 例えば,このアプリケーションで Java2 SDK1.2 のソースコード ディレクトリを見ると 図2 のようになる.


application
図2

このアプリケーションのおおまかな機能は下記のとおり.

  • ディレクトリ構造を左側にツリー表示する.
  • 右側には,ツリーで選択されたディレクトリが含むファイルやディレクトリの 情報(名前とサイズ)を表示する.ファイルやディレクトリがない場合は表示されない.

ファイルシステムの設計にデザインパターンを用いる例は、 書籍「パターンハッチング」でも取り上げられているが、ここでは一歩進めて アプリケーションのGUI を含めて考えてみる.GUI 部分は Swing パッケージを 利用するということも決めておこう.

それでは,早速アプリケーションの設計に入ることにする.


  Composite パターン


まず,ファイルシステムの構造について考えてみよう. ファイルシステムには ファイルとディレクトリの2つの要素がある. Java2 SDK1.2 では, java.io.File というファイルシステムの要素を表すクラスライブラリが 提供されているが,この例ではこのクラスを使わずに,ファイル, ディレクトリをそれぞれ File クラス,Directory クラスと定義する. (ここからは,File クラスはこれから作成するクラスを意味し, java.io.File とは区別する)

まず,ファイルと ディレクトリの関係を見てみよう.ディレクトリは, ファイルを子として含むことができる. また,ディレクトリはディレクトリ自身をも含むことができる.

filetree
図3

図3 のように,ファイルシステムは木構造で表されることが わかる. このようにオブジェクト間の関連(リンク)が木構造となる場合, Composite パターンが適応できる.

composite
図4

図4 は Composite パターンの構造を示す図である. Component クラスは Leaf クラスと Composite クラスの共通インターフェースを 定義する. これは,Leaf クラスと Composite クラスを一様に扱うためである. Composite クラスは,Component を子として複数保持する. つまり Composite クラスは Composite クラスをも子としてもつことができ, この構造のため木構造を表すことができるのである. Leaf クラスは 子をもたない クラスで,木構造での終端を意味する.

さて,図4 では,add() (Component を子のリストに追加する)メソッドや, remove() (Component を子リストから削除する)メソッドが Component クラス で定義されていることに注意しよう. 本来ならば, これらのメソッドは Leaf では必要がないため,Composite クラスでのみ定義する. では 何故 Component クラスで定義されているのであろうか. 実は,ここには Composite パターンが考慮しなければならない点がある. それは, Leaf クラスで必要がないメソッドを Comosite クラスで定義するか, あるいは,Component クラスで定義するかという点である. Component クラスで定義された場合は, Leafと Composite クラスを一様に扱うこと が可能であるが,Leafクラスでは無駄な処理を行う可能性がある. Composite クラス で定義する場合は,FileNode を処理する Client が キャストを行わなければ ならなくなるが,型安全であり無駄な処理を行われることはない. どちらを選択するかは,そのメソッドの使用頻度,重要度を考慮して定義すれば よい. もし,要素を一様に処理を行うことを重視する(つまり,Component で定義する) ならば,実装はメッセージの出力などデフォルトの実装を行えばよい.こうしておけば, 後に他の Leaf が追加されても,特に必要がない限り再度実装する必要はない. 一般的には,子をもたないクラス(Leaf クラスなど)に子を追加するといった処理は バグである可能性が高いため,その旨をメッセージ出力させるといった処理を行う. また,型安全な方(つまり,Composite で定義する)を選ぶなら,Component クラス に boolean isLeaf() といった子ノードをもつかもたないかをチェックするメソッド を用意し、それによって分岐すればよいだろう.

さて,Composite パターンをファイルシステムに適応してみよう.

filenodes
図5

Component にあたるものとして FileNode を定義する. FileNode には 名前の設定,取得,サイズの取得といったFileとDirectory の共通メソッド を定義する. また addChild() メソッドや removeChild() メソッドも 定義しておこう. これらのメソッドはFileNode では,エラー処理を 例外処理で行うことにしよう. 具体的には,RuntimeException を throw する. RuntimeException を使用したのは,addChild() を呼ぶ際に, try & catch しなくてもよいからである.

public class FileNode {

    private String name;  // 名前

    public String getName() {  // 名前を取得する.
        return name;
    }


    // 子供を追加する.

    public void addChild(FileNode child) {

        throw new RuntimeExcepiotn("Error : You can  not add child ");
    }
}

Composite クラスにあたるものは,子を保持する Directory である. Directory は, 子のリストを属性として持っている.

public class Directory {

    private List children = new Vector();   // 子の列.

    public void addChild(FileNode child) {
         children.add(child);                  // 子を追加する
    }
}

このように,Composite パターンは,木構造を表し,各ノードに対し 一様に処理を行いたい場合に有効である.

  Proxy パターン

次に,ファイルシステムにおけるシンボリックリンク機能を追加してみよう. シンボリックリンクとは,ファイルやディレクトリの実体が存在する場所を データとしてもっており,実体のように振る舞う機能である. つまり,実体の代理として働く. シンボリックリンクは,実体とは異なった 場所で定義できたり,実体とは別のアクセス権を定義できたり便利な機能である. Windowsにおいてはショートカットという名前で親しまれている.

このような代理の働きを行うものの作成には,Proxy パターンを適応する. Proxy パターンは実際のオブジェクトに対してそれをを参照している代理 をたて,クライアントに対してその代理によってあたかも実際のオブジェクトに アクセスしているかのようにみせることができる.

proxy1
図6

Proxy クラスは 実体を表す RealSubject クラスの代理をあらわし, RealSubject を参照している. Subject クラスは RealSubject と Proxy の 共通インターフェースを定義する. これは RealSubject にアクセスするために, Proxy を使用していることを意識させないためである. 図7はClient から要求があった場合のメッセージのフローを示す.

proxy2
図7

またProxy の実装例は以下である.


public void request() {

    realSubject.request();

}

さて,この Proxy パターンをシンボリックリンク機能に適応してみよう. シンボリックリンク機能を果たす クラスを Link と定義する.

link
図8

Link は Proxy パターンの Proxy にあたるものである. ファイルシステムでは,Link は FileNode を継承するため, FileNode が Proxy パターンの Subjectととなる. また Link が参照するものは,File, Directory, Link であるが, それらの共通インターフェースが FileNodeであるから RealSubject にあたる ものは FileNode としておけばよい. Link の実装は以下のように,各メソッドは, 参照(リンク)先の linkNode に対して処理を行う.

public class Link extends FileNode {


    private FileNode linkNode;

    public long getSize() {

        return linkNode.getSize();
    }


    public void addChild(FileNode child) {

        linkNode.addChild(child);  // linkNode に対して処理.
    }
}

このように,Proxy パターンは代理のクラスを作成する場合に適応できる. 今回は,Proxy パターンをシンボリックリンク機能の設計に利用したが, 他にも,様々な応用がきく.下記はその例である.

  • Proctection Proxy (Access Proxy)
    RealSubject のアクセスを制御する.
  • Virtual Proxy
    RealSubject が存在することを想定させ,RealSubject の生成を 要求があり次第生成する. こうすることで,メモリ空間や速度面でのパフォーマンスを高める.
  • Remote Proxy
    別アドレス空間上のオブジェクトに対してローカルな代理を提供する.

   Prototype パターン

次に,ファイルやディレクトリをコピーすることを考えてみよう. ファイルは,ファイルのデータ部分をコピーし,ディレクトリは, ディレクトリがもつ子のコピー(子がディレクトリであれば再帰的にコピー)することにする. さて,書類をコピーをするといった場合,みなさんはコピー機 を使うであろう(まさか,青刷りなどはしまい). ではコピー機は 一体何をしているのかというと,コピー元に描かれている絵や文字 (データ)を読み取り,新しい紙にそのデータを写しているのである. これと同じようにファイルやディレクトリに対してもコピーを行うと, Client 側の実装は以下のようになる.

public void Foo(File originalNode) {


    // File オブジェクトを生成.

    File newFile = new File();


    // コピー元(originalNode)のデータを取得し

  // それを新しいファイルに設定する.

    newFile.setValue(origiralFile.getValue());
}

しかし,この場合 Client 側では File の場合とDirectory の場合, もしくは,Link の場合と new するオブジェクトに応じて異なった 処理が必要になり分岐を行わなければならなくなる. また FileNode を継承するクラスがふえるたびにClient 側に処理を追記しなければならなくなる. そこで,Prototype パターンを適応することにする. Prototype パターンは名前のとおり,プロトタイプ(原型)をコピーするこ とで新たなオブジェクトを生成する.図9は,Prototype パターンの構造を示す図である.

prototype
図9

Prototype クラスは, Clinet が複製するためのインターフェースを提供する. ConcretePrototype は,Prototype で宣言された clone() メソッドを実装する. Clinet が 複製を行うには,


   p = prototype.clone();

とよぶだけでよい. また,ConcletePrototype の clone() は,


public Prototype clone() {



    // インスタンス作成.

    ConcretePrototype cp = new ConcretePrototype();



    // cp に対して新しいデータの設定の処理などを行う.



    return cp;

}

と実装する. これを,ファイルシステムのノードに適応してみよう. Prototype クラスとして, FileNode を適応する. ConcretePrototype クラスには File, Directory, Link クラスが相当する. また,Java では すべてのクラスの 上位クラスである Object クラスに clone() というメソッドがあるため, ここでは copy() という名前を使うことにする. File (FileNode のデフォルトの実装) や Directory の copy() の実装はそれぞれ, 以下のようになる.




-- File --

public FileNode copy() {

    return new FileNode(name);

}



-- Directory --

public FileNode copy() {



    Directory newDirectory = new Directory(name);



    for (int i = 0; i < children.size(); i++) {



       // 子の copy を作成し Directory の追加 (再帰) 

       newDirectory.addChild(getChild(i).copy());

    }

}


クライアントでの実装は


public void Foo(File originalNode) {



  // originalNode をコピーする.

  FileNode newNode = originalNode.copy();  // コピーをする.

}

となり,FileNode を継承するクラスを追加してもそのクラスに copy() メソッドを用意するだけで client 側には処理を記述する必要はなくなる. ここまでで,ファイルシステムの構造をまとめると,図10のようになる.

allfilenodes
図10

おおまかなファイルシステムの機能に関しては,図10のように, 非常にシンプルな設計でまとまる. うまくデザインパターンが活かされた例のひとつといえよう.

  Singleton パターン

さてここまでで,ファイルシステムのおおまかな構造が固まった. ここからは,ファイルシステムを利用したアプリケーションの設計を行うことに しよう.今回作成するアプリケーションはツリー表示とテーブル表示を 行うものであるが,コンソール表示アプリケーションも作るということも 考慮して,アプリケーションからファイルシステムへのインターフェース部分を 作成する. このインターフェース部分を FileSystem クラスと名付け, このクラスのインスタンスはアプリケーション内にただひとつのみ存在 することにしよう. 両方のアプリケーションの特徴から, FileSystem はおおまかに 以下の機能をもつ.

  • ルートの設定,取得.
  • カレントノード設定,取得.
  • 木構造の変更
    親(ParentNode)と,子(ChildNode)に対して,以下の処理を行う.
    ・ParentNode の 子リストに ChildNode を追加する.
    ・ParentNode の 子リストから ChildNode を削除する.
  • ノードの情報の取得
    指定されたノードの子のノードの情報を配列にまとめて取得する.

ここで,Windows など,ドライブが複数ある場合は,ルートを複数もつ ということで対応する. また,木構造に対して それを表示する GUI 部分が複数 ある場合はカレントも複数ある可能性があり,本来ならば,GUI 部分で管理した ほうがよいと思われるが,今回は GUI 部分はひとつであるということを前提とする.

さて,この FileSystem クラスのインタスタンスはアプリケーション 内にたったひとつ存在するのであるが,そのためにはどうすればよいのであろうか? それには,Singleton パターンを適応すればよい. Singleton パターンはあるクラスのインスタンスがひとつ しか存在しないことを保証し,それにアクセスするための グローバルな方法を提供する.Sigleton の実装の方法は次のようになる.

public class Singleton {

   private static Singleton instance;


   public static getInstance() {

       if (instance == null) {

           instance == new Singleton();
       }
       return instance;
   }
   private Singleton() { }
}

上記のように,コンスタラクタ は private アクセスにしておき, Singleton のインスタンス生成は,getInstance() メソッドを 必ず利用することを強制する. FileSystem での Singleton パターンの実装は,下記のとおりである.

public class FileSystem {

   private static FileSystem instance;


   public static getInstance() {

       if (instance == null) {

           instance == new FileSystem();
       }
       return instance;
   }
   private FileSystem() { }
}


  Adapter パターン

次に,このモデル部分を表示するGUI 部分を作成することを考えてみよう. GUI 部分には 最初の機能説明でも述べたように,Swing を使うことにする. Swing は Java のGUI クラスライブラリで,AWT と比べて非常に多くの 機能が提供されており,アプリケーションの表示の担当をすると共に,ユーザの マウス等によるイベントの配達をうけもつ. ここでは,ツリー表示を行うために, javax.swing.JTree ,また選択されたディレクトリの情報を表示するために, javax.swing.JTable を利用することにする. また,Swing の特徴として,内部で MVC (Model View Controler)構造を もっていることがあげられる. それを少し説明することにしよう.

まず,JTree に関連するクラスは javax.swing.tree パッケージに含まれて いるのだが,TreeModel インターフェースもそのパッケージに含まれている. TreeModel は MVCの Model にあたり,ツリーの構造を変更する (例えば,ノードを追加する,ノードを削除するといった)役割をもっている. JTree はこの TreeModel を参照していて,TreeModel の構造を表現する ための Viewの役割をもっており,また,マウスからのイベントをうけとり TreeModel にアクセスするといった Control の役割ももっている. JTable やその他の Swing コンポーネントに対しても UI 部分が View と Control の働きをもち,それに対して Model が存在するいう構造が とられている.

さて,TreeModel の機能を振り返ってみよう. 先程までに作成した FileSystem の機能の一部を共有していることがわかる. また,TreeModel のインタフェースを使って JTree は木構造を表示して いるため FileSystem のインターフェースを TreeModel に変換できれば JTree で表現できそうだということがわかる. そこで,Adapter パターンを適応する. Adapter パターンは,あるクラスの インターフェースを,クライアントが求める他のインターフェースへ変換する ことができる. 利用したい既存のクラスのインターフェースが異なっている 場合にこのパターンが適応できるのである.

adapter1
図11

図11は,Adapter パターンの構造を示す図である. Adapter は Target interface を実装し,Adaptee クラスを継承する. 実装は,以下のようになる.

public class Adapter extends Adaptee implements Target {


    public viod request() {

        specificRequest();  // Adaptee のメソッドを呼ぶ.
    }
}


また,Adapater パターンは,図12のようにも実装することもできる. Adapter は,Adaptee を参照する. Adapter への要求は,参照している Adaptee に変換する.

adapter2
図12

public class Adapter implements Target {

    private Adpatee adaptee;

    public void request() {  // adaptee へ処理を委譲する.

        adaptee.reqeustSpecificRequest();
    }
}

Adapter パターンは 図13のように,Client の要求をAdapter によって, Adaptee に変換しているのである。

adapter3
図13

このアプリケーションでは, TreeModel を implement した ExplorerModel を作成し,FileSystem を参照するようにした.

explorermodel

図14

public class FileSystem {

     // root Node の取得.

     public FileNode getRootNode() {

          return root;
     }
}


public class ExplorerModel implements TreeModel {

    private FileSystem model;

    public Object getRoot() {

        return model.getRootNode();
    }
}

上記実装では,JTree で扱う getRoot() メソッドを ExplorerModel の getRootNode() に変換している. このアプリケーションでは,その他,TableModel や, DefaultTreeSelection Model との連携にもこのパターンを適応する.


  Multicast パターンと Observer パターン

さて,Adapter パターンの説明で JTree と TreeModel は MVC フレームワークの View と Model にあたると説明した. MVC ではこの View と Model に Observer パターンを利用する. Observer パターンの構造を示すクラス図は,図15のとおりである.

observer
図15

Java では, Observer パターンのクラスがライブラリとして提供されており, Subject には java.uitl.Observable , Observer にはjava.util.Observer を利用することができる. ConcreteSubject や ConcreteObserverには それぞれこれらを継承して使うことになる. Observer パターンでは, Subject が更新されると, Observer の update() が呼ばれる. この時に,何が更新されたのかという情報を Observer が得るための方法には,pull 型と push 型がある.pull 型に おいては,ConcreteObserver は ConcreteSubject を直接知っているので, update の際に ConcreteSubject に更新内容を問い合わせる. push 型においては, ConcreteSubject が notify() (変更を通知する) を行う際に,更新内容をある Object につめこむみ,それをConcreteObserver に転送する方法を採る. push 型の メリットは,ConcreteObserver が ConcreteSubject を知らなく てもよい,つまり依存度が低くなるという点である. しかし,java.util.Observer の update() メソッドの 引数を見て頂ければわかるように,Observer 側では, Object クラス をうけとるため,ほとんどの場合 型安全ではないのである.

Multicast パターンは,こうしたpush 型 Observer パターンの 型安全版である といえる. Multicast では更新内容を Event クラスに含めて転送することになる.

multicast
図16

Source は Observer パターンの ConcreteSubject にあたり Listener は Observer にあたる. またSource と ConcreteListener に依存がないことに注意して頂きたい. Java では, Multicast パターン(Event を利用するので イベントモデル といわれる) が非常に多く利用されており,多くの Event の種類がある. それらの Event は java.util.EventObject クラスを継承しており, 新しい Event を作成するときにはそれを継承することになっている.

さて,話を アプリケーションの方に戻すことにしよう. Swing は MVC フレームワークでできていることから,それを 利用しない手はないであろう. JTree と TreeModel の連携にも Multicast パターン を適応する. JTree を継承した TreePanel に javax.swing.event.TreeModelListener interfaface を実装させる. TreeModelListener は Multicast パターンでいう Listener で,TreeModel が変更されたときに,その Event (具体的には javax.swing.event.TreeModelEvent)をうけとる. 下記は,モデル部分(TreeModel)に,File を追加しその変更を通知する実装例である. まず,Application (TreeModel , TreePanel を作成するクラス)が, TreeModel に Listener を登録する. Listener はこの場合, TreeModelListener を実装した TreePanelである.


   TreePanel treePanel = new TreePanel();

   TreeModel treeModel = new ExplorerModel();



   // TreeModel に TreeModelListener を登録.

   treeModel.addTreeModelListener(treePanel);

次に,ExplorerModel クラスに FileNode を追加するメソッドの実装である. 最初の行は,ExplorerModel が参照している FileSystem の変更である. 次に,Event として送るための情報を作成している. 最後に fireTreeNodesInserted() をよびだす.オブジェクトがイベントを送ることを, 「オブジェクトがイベントを発火する(fire)」 という. メソッド名に fire が ついているのはこういう意味である.

public void addFileNode(FileNode parent, FileNode child, int index) {


    // FileSystem の変更.

    model.addFileNode(parent, child, index);

    // Event として送るための情報作成.

    Object[] path = getPathToRoot(child);

    // Event 発火.

    fireTreeNodesInserted(parent, path, null, null);
}

上記最後では fireTreeNodesInserted()を呼び出しているが,この部分は, Event オブジェクトを作成し,登録されている TreeModelListerer の treeNodesInserted() メソッドを呼び Event を転送する.


protected void fireNodesInserted(Object source, Object[] path,

				int[] childIndices, Object[] children)

    // listeners の型は javax.swing.event.EventListenerList

    Object[] ls = listeners.getListenerList();  

    TreeModelEvnet e = null;

    for (int i = ls.length - 2 ; i>=0; i-=2) {

         if (ls[i] == TreeModelListener.class) {



             // Event 作成.

             e = new TreeModelEvent(source, path, childIndices, children);


             // Event を転送する.

	    ((TreeModelListener)ls[i+1]).treeNodesInserted(e);

         }
    }
}

最後に,Listener (この場合は TreePanel ) の メソッドでは Event から変更内容を取得し,処理を行う, ここではツリー上で Inseret されたノードが見えるようにする.(Expand する)


public class TreePanel extends JTree implements TreeModelListener {


    public void treeNodesInserted(TreeModelEvent e) {

    // ツリー上でInsere されたノードが見えるようにする.

    expandPath(e.getTreePath());
    }
}

これで,ファイルシステムの中核と Swing によるGUIを結合することができた. エクスプローラアプリケーション全体の構造を示すクラス図は図17のようになる.


図17

また,図18, 19は,ファイルをシステムに作成したときのメッセージの フロー図である. 図18, 19において,Application クラスは ファイルを システムに作成させる処理を発生させているもである. 実際は,JTree など がうけるマウスイベントによって発生する.


sequence
図18


activity
図19


さて,エクスプローラアプリケーションの設計はここで終了とするが, ここまで紹介したデザイパターンについてまとめてみることにする. 下記の表では,デザインパターンの種類とその適用可能性, また,各デザインパターンで定義されているクラスと,それぞれ のクラスに対するアプリケーション内で定義したクラスを表示した.

デザインパターン 適用可能性 デザインパターンのクラス 適応例
Composite オブジェクトが木構造であらわされる場合.
ComponentFileNode
CompositeDirectory
LeafFile
Proxy あるオブジェクトへの多機能で精巧な参照が必要なとき SubjectFileNode
ProxyLink
RealSubjectFileNode
Prototype オブジェクトの生成方法がシステムから独立している場合 PrototypeFileNode
ConcretePrototypeFile
Directory
Link
Singleton あるクラスに対してインスタンスが1つか存在しないことを保証した場合. SingletonFileSystem
Adapter 既存のクラスを利用したいがそのインターフェースが必要なインターフェースと一致していない場合 TargetTreeModel
AdapterExplorerModel
AdapteeFileSystem
Multicast 特定のオブジェクトが,他のオブジェクトの情報をうけとり その情報を型安全なオブジェクトにカプセル化して転送する場合 SourceExplorerModel
ListenerTreeModelListener
ConcreteListenerTreePanel
EventTreeModelEvent

これまで設計したアプリケーションでは, File や Direcotry はデザインパターンを解説するために, 独自に定義した抽象的なものであり,実存するファイルとは無関係なものである. そのため,アプリケーションは実存のファイルやディレクトリを表示するわけではない. もし,読者が実存のファイルやディレクトリを表示したいのであれば, Adapter パターンを利用して, FileNode を継承したjava.io.File との Adapter を定義すればよいであろう.

なお,完全なソースコードは, Explorer.zipからダウンロード可能である.


まとめ

この記事では,書籍「デザインパターン」に紹介されているパターンに絞って,パターンの読み方,パターンを利用した設計の仕方,および Java プログラムの中でのパターンの活用の仕方を解説した.

デザインパターンは,特に設計において大きな威力を発揮する. それと同時に,1つ1つのパターン利用には必ずトレードオフが発生し, 場面に応じてパターンを取捨選択しながら,複数のパターンを重ね合わせたり, カスタマイズしたりすることが必要である,ということが実感頂ければ幸いである.

ソフトウェアパターンには「デザインパターン」の他にも, 分析フェーズを対象とする「アナリシスパターン」,開発工程を対象とする「プロセスパターン」, システムの大粒度の構造を対象とする「アーキテクチャパターン」, プロジェクトが陥る危機的状態をカタログ化した「アンチパターン」などなど, さまざまなものが存在する.

多くの設計者が,リテラシとしてこうしたパターンの語彙を見につけて行くことが, チーム内コミュニケーションを助けることになるであろう.




この記事への評価にご協力をお願いします。

良かった 普通 イマイチ