ラベル デザインパターン の投稿を表示しています。 すべての投稿を表示
ラベル デザインパターン の投稿を表示しています。 すべての投稿を表示

2012年5月2日水曜日

Visitorパターン

Visitorパターンは、ツリー構造のデータ群(ディレクトリ構造を含めたファイル群みたいな)を処理する際に役立つ設計パターン。別にツリー構造に制限される訳ではないけど、実際それ以外で使わない気がする。

重要な登場人物

Element
データ構造の各要素を表すクラス。ファイルに当たるもの。
ObjectStructure
Elementの集合を扱うクラス。ディレクトリに当たるもの。
Visitor
データ構造の各要素を利用した処理の実装。ElementとObjectStructureに対して行う処理を実装する。


Visitorパターンでは、データ構造の階層の深さを意識することなく、またデータ構造側にデータ処理を一切書くことなく走査を行うことができる。データ走査は、Visitorをroot要素に渡すだけ。あとは勝手にデータ構造を舐め回して各Element(i.e ファイル)へ到達してくれる。


Visitorの実装

各要素に対して処理を行うためのAPIのみを定義した抽象クラスを定義する。

abstract void visit(ObjectStructure aDir);
abstract void visit(Element aFile);

具体的な処理内容に依ってVisitorの継承クラスを実装する。例えば処理内容が「ファイルの名前一覧を作成する」であった場合、FilenameVisitorクラスを作成して、次のような実装を行う。

/**
 * ディレクトリに対して行う処理を定義。
 * @param aDir ObjectStructure
 */
public void visit(ObjectStructure aDir)
{
  for (Element each : aDir.children())
  {
    each.accept(this);
  }
}

/**
 * ファイルに対して行う処理を定義。
 * @param aFile Element
 */
public void visit(Element aFile)
{
  logger.print(aFile.getName());
}

ObjectStructureの実装

Elementの集合を扱うクラスであり、以下のような実装になる。データ処理に関する実装は一切必要ないところがポイント。

/**
 * 子要素のリストを取得する。
 * @return 子要素リスト
 */
public List<Element> children()
{
  return this.children;
}

/**
 * データ処理インターフェースの受け口。
 * @param aVisitor Visitor
 */
public void accept(Visitor aVisitor)
{
  aVisitor.visit(this);
}

Elementの実装

最小の要素を示すクラスであり、以下のような実装になる。これもデータ処理に関する実装は一切必要ない。

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

/**
 * データ処理インターフェースの受け口。
 * @param aVisitor Visitor
 */
public void accpt(Visitor aVisitor)
{
  aVisitor.visit(this);
}


Bridgeパターン

今回はBridgeパターンを復習してみる。Bridgeパターンは文字通り橋渡しをするパターンであり、まずは機能と実装を分離することの大切さを思い出す必要がある。

具体的なケースとして以下が考えられる。

任意の処理を行うバッチプログラムを作成するためのクラス設計


基本クラス
すべてのバッチプログラムに共通の処理(ログ出力)を実装し、ビジネスロジックについては各継承クラスに任せる。
継承クラスA
ビジネスロジックとしてAという機能を実装
継承クラスB
ビジネスロジックとしてBという機能を実装

変更要求

このクラス設計にしたがって開発を行った場合、次のような変更要求があると途端にクラス設計の美しさが損なわれる。
  • CおよびDという機能を実現するバッチを追加してほしい。また、これらのバッチは起動時にあるファイルをロックし、終了時にロックを解除する必要がある。

要求への対応

基本クラスへロック機能を追加すると、既存のプログラムに影響がでてしまうので下記いずれかが対応として考えられる。
  1. 基本クラスにロック機能を追加した中間クラスを作成し、継承クラスCとDではこの中間クラスを継承する。
  2. ロック機能は別クラスに集約し、継承クラスCとDでは集約クラスのロック機能を利用する。

対応案の考察

  • 1番目の方法は、基本クラスから始まる「実装を追加するための継承」という流れに「機能を追加するための継承」という流れを混ぜてしまう。
  • 2番目の方法は、ロック機能とビジネスロジックの結合が強くなりがち。(やっぱロック機能いらないや、とか別のバッチにもロック機能つけて、とか言われた場合にビジネスロジックを含むクラスの修正が必要になってしまう)

さらに、Eという機能を実現するバッチを追加してほしい。また、このバッチの実行開始と終了時にEメールを送信してほしい。などと言われると余計面倒なことになる。

機能と実装の分離

このような場合に、Bridgeパターンに基づいて機能(を追加するための継承)と実装(を追加するための継承)を別の流れになるようなクラス設計にしておけば、変更要求に強い構造になる。
Abstraction
機能を追加する流れの最上位クラス。Implementorのインスタンスを保有する(=Bridge担当)
Implementor
実装を追加する流れの最上位クラス
Refined Abstraction
機能を追加する際にAbstractionを継承して作成するクラス。例で言うところのロック機能の追加はここで実装する。
Concrete Implementor
実装を追加する際にImplementorを継承して作成するクラス。例で言うところの継承クラスA~Dはこれのこと。

Mementoパターン

たまにはデザインパターンについて復習でもしてみる。今回はMementoパターン。
Mementoには記念品や思い出といった意味があり、ざっくりとまとめると「状態を保存しておけば、やり直しができるじゃないか!」といったもの。

Mementoパターンに登場するのは下記の3クラス。(便宜上クラスとしているが、役という表現のほうがしっくりくるかも)

Originator

Save(Mementoを作成する)とLoad(Mementoを読み込む)の機能を有するクラス。

Memento

セーブデータそのものを表すクラス。

Caretaker

Originatorに働きかけてSaveとかLoadを実行し、セーブデータ(Memento)を管理するクラス。


ポイント

  • Originator君はMementoを作成する人なので、Mementoのすべてに触れることができる。
  • 対してCaretakerさんはMementoを管理(永続化とか)するだけなので、Mementoの必要最低限についてのみ触れられれば良い。
って感じにすると、Mementoが美しくカプセル化される。