デザインパターン入門 | Visitor(ビジター)パターン
「訪問者(visitor)」と「訪問先(accepter)」の間で処理のやりとりが行われるデザインパターンが「Visitor(ビジター)パターン」です。
今回お話する内容には「コンポジット(Composite)」パターンのクラスが含まれていますので、まだ、「Composite(コンポジット)」パターンを学んでいない方は先に学習をしておくことをお勧めします。
「Composite(コンポジット)」パターンはそのまま利用しますが、今回の主役は、「訪問者(visitor)」と「訪問先(accepter)」ですので、これらの役割について注目しながら「Visitor(ビジター)パターン」について見ていきましょう。
「Visitor(ビジター)」と「Accepter(アクセプター)」
「Visitor(ビジター)」は、「訪問者」で、「Accepter(アクセプター)」は「訪問先」を表します。

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

「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(ビジター)」パターンについて見ていきたいと思います。
クラスとインターフェースの関係は下図のようになります。

「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」クラスのインスタンスを渡していますね。

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

「visit」メソッドでは、「Company」クラスと「Department」クラスの「toString」メソッドをオーバーライドして、「会社名 or 部署名」と「総売上高」を取得し表示をしています。
このように、「Acceptor」の「accept」メソッドが「Visitor」抽象クラスを継承したクラスのインスタンスを受け入れ、「accept」メソッドから受け入れたインスタンスの「visit」メソッドを呼び出すという仕組みが「Visitor」パターンで一番注目すべき部分です。
そして、「main」メソッドの実行結果は下記のようになります。
会社名:親会社 売上高:6800 会社名:子会社A 売上高:4100 会社名:子会社B 売上高:2700
なぜこのようなパターンがあるのかというと、「データ」と「処理」を分離し、「機能の追加が行いやすくなる」というメリットがあるからです。
例えば、「部署」の中の「プロジェクトチーム」ごとに総売上高を求めるといった機能追加があると、クラス間が「密」に関連を持っている場合に変更が大変になってしまいます。
そこで、「データ」部分と「処理」部分を分離し、それぞれのクラスの関連を「疎」に保つことで、「機能追加が容易な状態を保つ」ことができるようになります。
身近な事例を参考にして「どのようなパターンでVisitorが活用できるのか?」をぜひ考えてみてください。