JavaのVisitorパターン:多様なオブジェクト構造にわたる堅牢な操作の実装
Visitorデザインパターンの目的
オブジェクト構造の要素に対して実行される操作を表すため。Visitorを使用すると、操作対象の要素のクラスを変更することなく、新しい操作を定義できます。
現実世界の例を用いたVisitorパターンの詳細な説明
現実世界の例
Visitorデザインパターンの現実世界の例えとして、美術館のツアーガイドシステムが挙げられます。美術館では、絵画、彫刻、歴史的遺物など、さまざまな種類の展示物について学ぶために、ガイド付きツアーに参加できるとします。それぞれの展示物の種類には、専門のツアーガイドによって提供される異なる説明が必要です。
このシナリオでは、展示物はVisitorパターンの要素のようなものであり、ツアーガイドはVisitorのようなものです。美術館の構造は変更されませんが、展示物を変更することなく、新しい種類のツアー(操作)を持つ新しいガイドを追加できます。各ガイド(Visitor)は、展示物と対話する特定の方法を実装し、専門分野に応じた詳細な説明を提供することで、操作を操作対象のオブジェクトから分離します。
分かりやすく言うと
JavaのVisitorパターンは、さまざまなデータ構造のノードに対して実行できる操作を定義し、Javaアプリケーションの拡張性を高めます。
Wikipediaによると
オブジェクト指向プログラミングとソフトウェアエンジニアリングにおいて、Visitorデザインパターンは、アルゴリズムを操作対象のオブジェクト構造から分離する方法です。この分離の実用的な結果は、構造を変更することなく、既存のオブジェクト構造に新しい操作を追加できることです。
JavaにおけるVisitorパターンのプログラム例
軍隊のユニットを持つツリー構造を考えてみましょう。司令官の下には2人の軍曹がおり、各軍曹の下には3人の兵士がいます。階層がVisitorパターンを実装している場合、司令官、軍曹、兵士、または全員と対話する新しいオブジェクトを簡単に作成できます。
上記の軍隊ユニットの例では、まず`Unit`と`UnitVisitor`の基本型があります。
public abstract class Unit {
private final Unit[] children;
public Unit(Unit... children) {
this.children = children;
}
public void accept(UnitVisitor visitor) {
Arrays.stream(children).forEach(child -> child.accept(visitor));
}
}
public interface UnitVisitor {
void visit(Soldier soldier);
void visit(Sergeant sergeant);
void visit(Commander commander);
}
次に、具体的なユニットである`Commander`、`Sergeant`、`Soldier`があります。
public class Commander extends Unit {
public Commander(Unit... children) {
super(children);
}
@Override
public void accept(UnitVisitor visitor) {
visitor.visit(this);
super.accept(visitor);
}
@Override
public String toString() {
return "commander";
}
}
public class Sergeant extends Unit {
public Sergeant(Unit... children) {
super(children);
}
@Override
public void accept(UnitVisitor visitor) {
visitor.visit(this);
super.accept(visitor);
}
@Override
public String toString() {
return "sergeant";
}
}
public class Soldier extends Unit {
public Soldier(Unit... children) {
super(children);
}
@Override
public void accept(UnitVisitor visitor) {
visitor.visit(this);
super.accept(visitor);
}
@Override
public String toString() {
return "soldier";
}
}
具体的なVisitorである`CommanderVisitor`、`SergeantVisitor`、`SoldierVisitor`は次のとおりです。
@Slf4j
public class CommanderVisitor implements UnitVisitor {
@Override
public void visit(Soldier soldier) {
// Do nothing
}
@Override
public void visit(Sergeant sergeant) {
// Do nothing
}
@Override
public void visit(Commander commander) {
LOGGER.info("Good to see you {}", commander);
}
}
@Slf4j
public class SergeantVisitor implements UnitVisitor {
@Override
public void visit(Soldier soldier) {
// Do nothing
}
@Override
public void visit(Sergeant sergeant) {
LOGGER.info("Hello {}", sergeant);
}
@Override
public void visit(Commander commander) {
// Do nothing
}
}
@Slf4j
public class SoldierVisitor implements UnitVisitor {
@Override
public void visit(Soldier soldier) {
LOGGER.info("Greetings {}", soldier);
}
@Override
public void visit(Sergeant sergeant) {
// Do nothing
}
@Override
public void visit(Commander commander) {
// Do nothing
}
}
最後に、Visitorの威力を実際に示すことができます。
public static void main(String[] args) {
var commander = new Commander(
new Sergeant(new Soldier(), new Soldier(), new Soldier()),
new Sergeant(new Soldier(), new Soldier(), new Soldier())
);
commander.accept(new SoldierVisitor());
commander.accept(new SergeantVisitor());
commander.accept(new CommanderVisitor());
}
プログラム出力
14:58:06.115 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier
14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier
14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier
14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier
14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier
14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier
14:58:06.118 [main] INFO com.iluwatar.visitor.SergeantVisitor -- Hello sergeant
14:58:06.118 [main] INFO com.iluwatar.visitor.SergeantVisitor -- Hello sergeant
14:58:06.118 [main] INFO com.iluwatar.visitor.CommanderVisitor -- Good to see you commander
現実世界の例を用いたVisitorパターンの詳細な説明

JavaでVisitorパターンを使用する場面
Visitorパターンを使用する場合
- クラスを変更せずに類似したオブジェクトのグループにわたって効率的に操作を実行する必要がある場合、およびクラスをこの操作で汚染することを避けたい場合に、JavaでVisitorデザインパターンを実装します。
- クラス構造が安定しているが、変更せずに構造に対して新しい操作を実行する必要がある場合に使用します。
- クラスのセットが固定されており、操作のみを拡張する必要がある場合に便利です。
Visitorパターン Javaチュートリアル
JavaにおけるVisitorパターンの現実世界のアプリケーション
- コンパイラの設計。Visitorパターンは、プリティプリント、意味チェックなどの操作に使用できます。
- 抽象構文木(AST)の処理。
- ドキュメント構造の処理(例:HTML、XML)。
- Apache Wicket コンポーネントツリー、MarkupContainerを参照してください
- javax.lang.model.element.AnnotationValue と AnnotationValueVisitor
- javax.lang.model.element.Element と Element Visitor
- java.nio.file.FileVisitor
Visitorパターンの利点と欠点
利点
- 新しい操作の追加の簡素化:既存のコードを変更することなく、新しいVisitorを追加できるため、新しい操作の追加は簡単です。
- 単一責任の原則:Visitorパターンを使用すると、関連する振る舞いを1つのクラスに移動できます。
- 開閉原則:要素は変更に対して閉じられたままで、Visitorは拡張に対して開かれています。
欠点
- 新しい要素クラスの追加:新しいタイプの要素を追加する必要がある場合、Visitorインターフェースとそのすべての具体的なVisitorを変更する必要があります。
- 循環依存関係:複雑なシステムでは、このパターンはVisitorと要素クラスの間に循環依存関係をもたらす可能性があります。
- カプセル化の破壊:Visitorパターンでは、Visitorがジョブを実行できるように、要素クラスが十分な詳細を公開する必要があるため、カプセル化が損なわれる可能性があります。
関連するJavaデザインパターン
- Composite:Visitorパターンは、多くの場合、Visitorが複合構造に対して操作を実行できるCompositeパターンと組み合わせて使用されます。
- Interpreter:Visitorを使用して、Interpreterパターンの非終端式を実装できます。
- Strategy:Visitorは、操作対象として設計されていないオブジェクトで戦略を機能させる方法と見なすことができます。