概要

Eclipseのソースコード解析パーサASTParser を調べてたら、Visitorパタンが使われてたので、いままでめんどくさくってさぼってたVisitorパタンのお勉強をしました。そのメモです。

Visitorパタンは、あるモデルに対して動的にオペレーションを決定するためのパタンです。なんのこっちゃという感じですが、たとえば

  • ディレクトリ構造が与えられて、ファイルに対してはそのファイルのサイズを返す、ディレクトリに対してはそのディレクトリ内のファイルサイズの合計を返す
  • EclipseのASTparserがパースしたソースコードに対して、JavaDoc?コメントの場合は○○、フィールドの場合は△△などのオペレーションを行う。

などがありそうです。

サンプル

ディレクトリとファイルの話でサンプルを作ってみます。あるディレクトリ構造が与えられるので、そのディレクトリ構造というモデルに対してVisitorインタフェースの実装クラスをセットします。Visitorインタフェースには、ファイル用のvisitメソッド、ディレクトリ用のvisitメソッドが宣言されているので、それぞれのメソッドを実装すればよいという感じです。

/**
 * ファイルとディレクトリのインタフェース。
 * 
 * @author Masatomi KINO
 * @version $Revision$
 */
interface Entry {
  /**
   * Visitorを受け入れるメソッド。だいたいvisitor.visit(this);みたいな処理になる。
   * 
   * @param visitor
   */
  void accept(Visitor visitor);

  String getName();
}
/**
 * ディレクトリ
 * 
 * @author Masatomi KINO
 * @version $Revision$
 */
class Directory implements Entry {
  private String name;
  private List<Entry> list = new ArrayList<Entry>();
  public Directory(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public void addEntry(Entry e) {
    list.add(e);
  }
  public List<Entry> getFiles() {
    return list;
  }
  public void accept(Visitor visitor) {
    visitor.visit(this); // void visit(Directory directory); が呼ばれてる。
    // ↓ さらに、自分が持ってるEntryたちのacceptも呼んであげる。
    Iterator<Entry> e = list.iterator();
    while (e.hasNext()) {
      Entry element = e.next();
      element.accept(visitor);
    }
  }

}
/**
 * ファイル
 * 
 * @author Masatomi KINO
 * @version $Revision$
 */
class File implements Entry {
  private String name;
  public File(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public void accept(Visitor visitor) {
    visitor.visit(this); // void visit(File file); が呼ばれてる。
  }
}
interface Visitor {
  /**
   * ディレクトリ構造を走査してったときに、 ディレクトリだったら このメソッドが呼ばれる
   * 
   * @param directory
   */
  void visit(Directory directory);

  /**
   * ディレクトリ構造を走査してったときに、 ファイルだったら このメソッドが呼ばれる
   * 
   * @param file
   */
  void visit(File file);
}

さて準備ができました。ではディレクトリ構成を作って、そこにVisitorをセットしようと思います。以下のようにしました。

class Client {
  public void method() {
    Directory directory = new Directory("ROOT");
    directory.addEntry(new File("ほげ.txt"));

    Directory dir = new Directory("child");
    dir.addEntry(new File("ふが.txt"));
    dir.addEntry(new File("ほげほげ.txt"));

    directory.addEntry(dir);

    // Visitorをセット。
    directory.accept(new Visitor() {
      public void visit(Directory directory) {
        System.out.println(directory.getName());
      }

      public void visit(File file) {
        System.out.println(file.getName());
      }
    });
  }
}

実行結果は以下の通りです。

ROOT
ほげ.txt
child
ふが.txt
ほげほげ.txt

まとめ

Visitorパタンを設計する側はともかくとして、使う側つまりVisitorの実装クラスを作る側は、モデル内の各要素(FileとかDirectoryとか)用のメソッド

void visit(Directory directory);
void visit(File file);

を実装し、モデルにその実装クラスを

directory.accept(new Visitor() {.....

でセットしさえすれば、後は勝手にフレームワーク側が実装クラスのメソッドを呼び出してくれるんですね。

関連リンク


この記事は

選択肢 投票
おもしろかった 3  
そうでもない 1  

現在のアクセス:4066


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2007-08-24 (金) 11:08:54 (4494d)