デザインパターン ~Visitor~


1. はじめに

GoFのデザインパターンにおける、Visitorパターンについてまとめます。

2. Visitorパターンとは

  • Visitorという英単語は、訪問者という意味になります。
  • Visitorパターンは、データ構造と処理を分離する方式です。
  • データ構造の中をめぐり歩く訪問者クラスを用意し、訪問者クラスに処理を任せます。すると、新しい処理を追加したいときは新しい訪問者を作ればよいことになります。そして、データ構造の方は、訪問者を受け入れてあげればよいのです。
  • GoFのデザインパターンでは、振る舞いに関するデザインパターンに分類されます。

3. サンプルクラス図

4. サンプルプログラム

ディレクトリ、ファイルの一覧を表示するプログラムです。

4-1. Elementインターフェース

Visitorクラスのインスタンスを受け入れるデータ構造を表すインターフェースです。

Element.java
public interface Element {
    public abstract void accept(Visitor v);
}

4-2. Entryクラス

FileやDirectoryの基底となるクラスです。Elementインターフェースを実装します。

Entry.java
public abstract class Entry implements Element {

    public abstract String getName();

    public String toString() {
        return getName();
    }
}

4-3. Fileクラス

ファイルを表すクラスです。Visitorの受け入れ役になります。

File.java
public class File extends Entry {

    private String name;

    public File(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void accept(Visitor v) {
        v.visit(this);
    }
}

4-4. Directoryクラス

ディレクトリを表すクラスです。Visitorの受け入れ役になります。

Directory.java
import java.util.ArrayList;
import java.util.Iterator;

public class Directory extends Entry {

    private String name;
    private ArrayList<Entry> dir = new ArrayList<Entry>();

    public Directory(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public Entry add(Entry entry) {
        dir.add(entry);
        return this;
    }

    public Iterator<Entry> iterator() {
        return dir.iterator();
    }

    public void accept(Visitor v) {
        v.visit(this);
    }
}

4-5. Visitorクラス

ファイルやディレクトリを訪れる訪問者を表す抽象クラスです。

Visitor.java
public abstract class Visitor {
    public abstract void visit(File file);
    public abstract void visit(Directory directory);
}

4-6. ListVisitorクラス

ファイルやディレクトリの一覧を表示するクラスです。

ListVisitor.java
import java.util.Iterator;

public class ListVisitor extends Visitor {

    // 現在注目しているディレクトリ名
    private String currentdir = "";

    // ファイルを訪問したときに呼ばれる
    public void visit(File file) {
        System.out.println(currentdir + "/" + file);
    }

    // ディレクトリを訪問したときに呼ばれる
    public void visit(Directory directory) {
        System.out.println(currentdir + "/" + directory);
        String savedir = currentdir;
        currentdir = currentdir + "/" + directory.getName();
        Iterator<Entry> it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            entry.accept(this);
        }
        currentdir = savedir;
    }
}

4-7. Mainクラス

メイン処理を行うクラスです。

Main.java
public class Main {
    public static void main(String[] args) {

        Directory workspaceDir = new Directory("workspace");
        Directory compositeDir = new Directory("Visitor");
        Directory testDir1 = new Directory("test1");
        Directory testDir2 = new Directory("test2");
        workspaceDir.add(compositeDir);
        workspaceDir.add(testDir1);
        workspaceDir.add(testDir2);

        File element = new File("Element.java");
        File entity = new File("Entity.java");
        File file = new File("file.java");
        File directory = new File("Directory.java");
        File visitor = new File("Visitor.java");
        File listVisitor = new File("ListVisitor.java");
        File main = new File("main.java");
        compositeDir.add(element);
        compositeDir.add(entity);
        compositeDir.add(file);
        compositeDir.add(directory);
        compositeDir.add(visitor);
        compositeDir.add(listVisitor);
        compositeDir.add(main);

        workspaceDir.accept(new ListVisitor());
    }
}

4-8. 実行結果

/workspace
/workspace/Visitor
/workspace/Visitor/Element.java
/workspace/Visitor/Entity.java
/workspace/Visitor/file.java
/workspace/Visitor/Directory.java
/workspace/Visitor/Visitor.java
/workspace/Visitor/ListVisitor.java
/workspace/Visitor/main.java
/workspace/test1
/workspace/test2

5. メリット

Visitorパターンは処理を複雑にしているだけで、「繰り返しの処理が必要ならデータ構造の中にループ処理を書けばいいのではないか?」と感じます。
Visitorパターンの目的は、データ構造と処理を分離することです。データ構造は、要素を集合としてまとめたり、要素間を繋いだりしてくれるものです。その構造を保持しておくことと、その構造を基礎とした処理を書くことは別のものです。
訪問者役(ListVisitor)は、受け入れ役(File、Directory)とは独立して開発することができます。つまり、Visitorパターンは、受け入れ役(File、Directory)クラスの部品としての独立性を高めていることになります。もし、処理の内容をFileクラスやDirectoryクラスのメソッドとして実装してしまうと、新しい処理を追加して機能拡張するたびにFileクラスやDirectoryクラスを修正しなければならなくなります。

6. GitHub

7. デザインパターン一覧

8. 参考

今回の記事、及びサンプルプログラムは、以下の書籍を元に作成させて頂きました。

大変分かりやすく、勉強になりました。感謝申し上げます。
デザインパターンやサンプルプログラムについての説明が詳細に書かれていますので、是非書籍の方もご覧ください。