デザインパターン入門 | Visitor(ビジター)パターン

「訪問者(visitor)」と「訪問先(accepter)」の間で処理のやりとりが行われるデザインパターンが「Visitor(ビジター)パターン」です。

今回お話する内容には「コンポジット(Composite)」パターンのクラスが含まれていますので、まだ、「Composite(コンポジット)」パターンを学んでいない方は先に学習をしておくことをお勧めします。

→「コンポジット(Composite)」パターン」

「Composite(コンポジット)」パターンはそのまま利用しますが、今回の主役は、「訪問者(visitor)」と「訪問先(accepter)」ですので、これらの役割について注目しながら「Visitor(ビジター)パターン」について見ていきましょう。

「Visitor(ビジター)」と「Accepter(アクセプター)」

「Visitor(ビジター)」は、「訪問者」で、「Accepter(アクセプター)」は「訪問先」を表します。

ビジターパターン1

「Visitor(ビジター)」は抽象クラス、「Accepter(アクセプター)」はインターフェースとして作成していきます。

ビジターパターン2

「Visitor(ビジター)」抽象クラスは下記のようになります。(プログラム言語:Java)

public abstract class Visitor {
	public abstract void visit(Department department) ;
	public abstract void visit(Company company) ;
}

「Accepter(アクセプター)」インターフェースは下記のようになります。

public interface Accepter {
	public abstract void accept(Visitor visitor);
}

【参考事例】「会計士」と「会社」

今回は「会計士(Account)」と「会社(Company)」を例にして「Visitor(ビジター)」パターンについて見ていきたいと思います。

クラスとインターフェースの関係は下図のようになります。

ビジターパターン3

「AccountVisitor」クラスは、「会計士」を表すクラスですが、このメソッドは「Visitor」抽象クラスを継承しています。

「AccountVisitor」クラスの実装内容は下記のようになります。

public class AccountantVisitor extends Visitor{
	@Override
	public void visit(Department department) {
		System.out.println( department );
	}

	@Override
	public void visit(Company company) {
		System.out.println( company );
	}
}

このクラスでは、「visit」メソッドをオーバーライドしていますが、内容は「Company」クラス、「Department」クラスの内容を出力するだけとなっています。

次に「Entry」クラスは、「Acceptor」インターフェースを実装しています。

public abstract class Entry implements Accepter {
	public abstract String getName();
	public abstract int getEarnings();
}

このクラスでは、

getName
会社または部署名を取得
getEarnings
会社または部署の「総売上金額」を取得

の2つの抽象メソッドが定義されています。

そして、この「Entry」クラスを継承しているのが、「Company」クラスと「Department」クラスです。

「会社」を表す「Company」クラスは下記のようになります。

public class Company extends Entry{
	// 会社名
	private String name;

	// 子会社または部署を保存
	private ArrayList components = new ArrayList();

	// コンストラクタ
	public Company(String name) {
		super();
		this.name = name;
	}

	// 子会社または部署を追加
	public Entry add(Entry component) {
		components.add(component);
		return this;
	}

	// 会社名を取得
	@Override
	public String getName() {
		return this.name;
	}

	// 総売上高を取得
	@Override
	public int getEarnings() {
		int earnings = 0;
		Iterator it = components.iterator();
		while ( it.hasNext() ) {
			Entry component = (Entry)it.next();
			earnings += component.getEarnings();
		}
		return earnings;
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

	@Override
	public String toString() {
		return "会社名:" + this.getName() + " 売上高:" + this.getEarnings();
	}
}

ここで注目したいのが「accept」メソッドです。

「Visitor型のインスタンス」を受け取り、そのインスタンスの「visit」メソッドを呼び出しています。

その際に「自分自身(acceptor)」を引数に渡しています。

この部分が「Visitor(ビジター)」パターンの中で重要な仕組みとなっています。

次に「部署」を表す「Department」クラスは下記のようになります。

public class Department extends Entry{
	// 部署名
	private String name;

	// 売上高
	private int earnings;

	// コンストラクタ
	public Department(String name, int earnings) {
		super();
		this.name = name;
		this.earnings = earnings;
	}

	// 部署名を取得
	@Override
	public String getName() {
		return this.name;
	}

	// 部署の売上高を取得
	@Override
	public int getEarnings() {
		return this.earnings;
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

	@Override
	public String toString() {
		return "部署名" + this.getName() + " 売上高:" + this.getEarnings();
	}
}

「accept」メソッドは「Department」クラスと同様となっていますね。

これで、必要な「クラス・インターフェース」は全て揃いました。

これらのクラスを利用する「main」メソッドは、下記のようになります。

public class Main {
	public static void main(String[] args) {
		// 親会社の作成
		Company rootCompany = new Company("親会社");

		// 子会社の作成
		Company subsidiaryA = new Company("子会社A");
		Company subsidiaryB = new Company("子会社B");

		//孫会社の作成
		Company sub_subsidiaryA = new Company("孫会社A");
		Company sub_subsidiaryB = new Company("孫会社B");
		Company sub_subsidiaryC = new Company("孫会社C");
		Company sub_subsidiaryD = new Company("孫会社D");

		// 親会社に子会社を登録
		rootCompany.add(subsidiaryA);
		rootCompany.add(subsidiaryB);

		// 子会社Aに孫会社を登録
		subsidiaryA.add(sub_subsidiaryA);
		subsidiaryA.add(sub_subsidiaryB);

		// 子会社Bに孫会社を登録
		subsidiaryB.add(sub_subsidiaryC);
		subsidiaryB.add(sub_subsidiaryD);


		// 子会社Aの部署を作成
		subsidiaryA.add(new Department("住宅販売事業部",3000));

		// 孫会社Aの部署を作成
		sub_subsidiaryA.add(new Department("住宅資材生産事業部",900));

		// 孫会社Bの部署を作成
		sub_subsidiaryB.add(new Department("不動産管理事業部",200));

		// 子会社Bの部署を作成
		subsidiaryB.add(new Department("家電販売事業部",2000));

		// 孫会社Cの部署を作成
		sub_subsidiaryC.add(new Department("家電生産事業部",500));

		// 孫会社Dの部署を作成
		sub_subsidiaryD.add(new Department("リペア事業部",200));

		// 会計士オブジェクトの作成
		AccountantVisitor accountantVisitor = new ();

		//グループ会社の総売上高を取得
		rootCompany.accept(accountantVisitor);

		//子会社Aの総売上高を取得
		subsidiaryA.accept(accountantVisitor);

		//子会社Bの総売上高を取得
		subsidiaryB.accept(accountantVisitor);
	}
}

「AccountantVisitor」クラスのインスタンスを作り、「Company」クラスと「Department」クラスの「accept」メソッドの引数に「AccountantVisitor」クラスのインスタンスを渡していますね。

ビジターパターン4

「Company」クラスと「Department」クラスの「accept」メソッドが呼び出されると「AccountantVisitor」クラスの「visit」メソッドが実行されます。

ビジターパターン5

「visit」メソッドでは、「Company」クラスと「Department」クラスの「toString」メソッドをオーバーライドして、「会社名 or 部署名」と「総売上高」を取得し表示をしています。

このように、「Acceptor」の「accept」メソッドが「Visitor」抽象クラスを継承したクラスのインスタンスを受け入れ、「accept」メソッドから受け入れたインスタンスの「visit」メソッドを呼び出すという仕組みが「Visitor」パターンで一番注目すべき部分です。

そして、「main」メソッドの実行結果は下記のようになります。

会社名:親会社 売上高:6800
会社名:子会社A 売上高:4100
会社名:子会社B 売上高:2700

なぜこのようなパターンがあるのかというと、「データ」と「処理」を分離し、「機能の追加が行いやすくなる」というメリットがあるからです。

例えば、「部署」の中の「プロジェクトチーム」ごとに総売上高を求めるといった機能追加があると、クラス間が「密」に関連を持っている場合に変更が大変になってしまいます。

そこで、「データ」部分と「処理」部分を分離し、それぞれのクラスの関連を「疎」に保つことで、「機能追加が容易な状態を保つ」ことができるようになります。

身近な事例を参考にして「どのようなパターンでVisitorが活用できるのか?」をぜひ考えてみてください。

HOMEへ