1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GoFデザインパターン】Visitor(ビジター)パターンをJavaで理解する

1
Last updated at Posted at 2026-10-03

はじめに

本記事は、GoFデザインパターン解説シリーズの1つです。

今回でいよいよGoFの23パターン最後、Visitor(ビジター)パターンを解説します。

「データ構造」と「データに対する処理」を分離するという、少しトリッキーですが強力な考え方を学びます。

解決したい課題

複数の種類のクラス(例えば図形のCircle・Rectangle・Triangle)からなるデータ構造に対して、新しい種類の処理(操作)を追加したいが、各クラスのコード自体は変更したくない(できない)という場面があります。

素朴な方法として、各クラスに直接メソッドを追加する(CircleにcalculateArea()、exportToJson()、renderToScreen()...とメソッドを増やしていく)実装をすると、新しい操作を追加するたびに、全種類のクラスに同じような追加が必要になり、クラス自体がどんどん肥大化していきます。

また、サードパーティ製のライブラリが提供するクラスなど、そもそも自分ではコードを編集できないクラスに対して、新しい操作を追加すること自体ができません。

Visitorパターンは、「処理(操作)」を、データ構造のクラス自体からは切り離し、外部の「訪問者(Visitor)」オブジェクトとして表現することで、この問題を解決します。

新しい操作を追加したい場合は、新しいVisitorクラスを1つ追加するだけで済み、既存のデータ構造クラスのコードは変更不要になります。

image.png

クラス構成

  • Element(要素): Visitorを受け入れるメソッド(accept())を持つ、共通インターフェース
  • ConcreteElement(具体的な要素): 実際のデータ構造を表す、具体的なクラス群
  • Visitor(ビジター): 各ConcreteElementに対応する処理メソッドを持つ、共通インターフェース
  • ConcreteVisitor(具体的なビジター): 実際の処理内容を実装する、具体的なVisitorクラス

Javaでの実装例

図形(Circle・Rectangle)に対して、「面積計算」「JSON出力」という2種類の操作をVisitorとして実装してみます。

Element:図形共通のインターフェース

public interface Shape {
    // 「自分自身をVisitorに渡す」というのが特徴的なポイント(ダブルディスパッチ)
    void accept(ShapeVisitor visitor);
}

ConcreteElement:具体的な図形クラス

public class Circle implements Shape {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    @Override
    public void accept(ShapeVisitor visitor) {
        visitor.visit(this); // 自分の具体的な型(Circle)をVisitorに渡す
    }
}

public class Rectangle implements Shape {
    private final double width;
    private final double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }

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

Visitor:共通インターフェース

public interface ShapeVisitor {
    void visit(Circle circle);
    void visit(Rectangle rectangle);
}

ConcreteVisitor:具体的な処理群

public class AreaCalculatorVisitor implements ShapeVisitor {
    private double totalArea = 0;

    @Override
    public void visit(Circle circle) {
        double area = Math.PI * circle.getRadius() * circle.getRadius();
        System.out.println("円の面積: " + area);
        totalArea += area;
    }

    @Override
    public void visit(Rectangle rectangle) {
        double area = rectangle.getWidth() * rectangle.getHeight();
        System.out.println("四角形の面積: " + area);
        totalArea += area;
    }

    public double getTotalArea() {
        return totalArea;
    }
}

public class JsonExportVisitor implements ShapeVisitor {
    @Override
    public void visit(Circle circle) {
        System.out.println("{\"type\": \"circle\", \"radius\": " + circle.getRadius() + "}");
    }

    @Override
    public void visit(Rectangle rectangle) {
        System.out.println("{\"type\": \"rectangle\", \"width\": " + rectangle.getWidth()
            + ", \"height\": " + rectangle.getHeight() + "}");
    }
}

利用側のコード

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Shape> shapes = List.of(
            new Circle(5.0),
            new Rectangle(4.0, 6.0)
        );

        System.out.println("--- 面積計算 ---");
        AreaCalculatorVisitor areaVisitor = new AreaCalculatorVisitor();
        for (Shape shape : shapes) {
            shape.accept(areaVisitor); // 図形側は「自分をvisitorに渡す」だけ
        }
        System.out.println("合計面積: " + areaVisitor.getTotalArea());

        System.out.println();

        System.out.println("--- JSON出力 ---");
        JsonExportVisitor jsonVisitor = new JsonExportVisitor();
        for (Shape shape : shapes) {
            shape.accept(jsonVisitor);
        }
    }
}

実行結果:

--- 面積計算 ---
円の面積: 78.53981633974483
四角形の面積: 24.0
合計面積: 102.53981633974483

--- JSON出力 ---
{"type": "circle", "radius": 5.0}
{"type": "rectangle", "width": 4.0, "height": 6.0}

Circle・Rectangleクラスには、「面積を計算する」「JSONに変換する」といった処理のロジックは一切書かれていません。

それぞれの処理はAreaCalculatorVisitor・JsonExportVisitorという、独立したVisitorクラスに実装されています。

新しい操作(例えば「SVGとして描画する」)を追加したい場合も、新しいShapeVisitor実装クラスを1つ追加するだけでよく、Circle・Rectangleのコードは一切変更する必要がありません。

なぜaccept(visitor)が必要なのか(ダブルディスパッチ)

「visitor.visit(shape)のように、直接Visitor側から図形を渡せばよいのでは?」と思うかもしれません。

しかし、shapesリストの要素の型はShape(インターフェース)であり、Javaのメソッドオーバーロードの解決はコンパイル時の静的型に基づいて行われるため、visitor.visit(shape)と書いてしまうと、実際の中身がCircleでもRectangleでも、コンパイル時の型Shapeに対応するオーバーロードを解決できず、意図通りに動きません。

そこで、各ConcreteElement自身にaccept(visitor)を実装させ、その中でvisitor.visit(this)と自分自身の具体的な型で呼び出すことで、正しいオーバーロードが選ばれるようにしています。

この「2段階の呼び出し」の仕組みはダブルディスパッチと呼ばれ、Visitorパターンの核心的なテクニックです。

image.png

使用場面

  • コンパイラ・インタープリターにおける、構文木(AST)に対する複数の処理(型チェック、コード生成、最適化など)
  • 複数の種類の要素からなるデータ構造に対して、頻繁に新しい種類の「処理」を追加する必要があるが、要素の種類(クラス)自体はあまり増えない場合
  • 既存のクラス群(変更できないライブラリのクラスなど)に、後から新しい操作を追加したい場合

注意点

Visitorパターンは、要素の種類(ConcreteElement)が増えるたびに、すべてのVisitor実装に新しいメソッドを追加しなければならないという、Strategyパターンなどとは逆方向のトレードオフを持ちます。

image.png

「処理の種類は増えるが、データ構造の種類はあまり増えない」場合に向いているパターンであり、逆に「データ構造の種類が頻繁に増える」場合には不向きです。

また、ダブルディスパッチの仕組みはやや理解しづらく、GoFの23パターンの中でも学習コストが高いパターンの1つとされています。

まとめ

項目 内容
解決する課題 既存のデータ構造クラス群に対して、新しい処理を、クラス自体を変更せずに追加したい
実現方法 処理をVisitorオブジェクトとして切り出し、各要素にaccept()経由でダブルディスパッチさせる
メリット 新しい操作の追加が、新しいVisitorクラスの追加だけで完結する
注意点 要素の種類が増えるたびに、全Visitor実装の修正が必要になる(トレードオフの向きがStrategy等と逆)

おすすめ書籍

GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。

Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]

※本リンクはアフィリエイトリンクを含みます。

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?