📖

★ Java | Visitor Pattern

2025/03/02に公開

Java

Visitor Pattern概要

  • アルゴリズムをオブジェクトの構造から分離して、新しい操作を柔軟に追加できるデザインパターン
  • 分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。

Visitor Pattern特徴

Visitor Pattern特徴 内容
データ構造と処理の分離 データ構造を変更せずに新しい操作を追加できる。
拡張性 新しいVisitorを追加することで、既存の要素に新しい操作を適用できる。
要素の知識不要 要素はVisitorの存在を認識する必要がない。

図形描画処理でのVisitor Pattern適用例

  • Visitorパターンを使用することで、図形の構造を変更せずに新しい操作(この場合は描画)を追加することができる。
  • 以下のサンプルではVisitorAcceptorという名前を使い、Visitorパターンの構造を明確にしている。

1. 図形のインターフェース(Acceptor)

  • ShapeAcceptorインターフェース:
    すべての図形が実装するインターフェースで、acceptメソッドを定義している。
    このメソッドはVisitorを受け入れるために使用される。
interface ShapeAcceptor {
    void accept(ShapeVisitor visitor);
}

2. 具体的な図形クラス(Acceptorの実装)

  • 具体的な図形クラス:
    CircleRectangleShapeAcceptorインターフェースを実装し、それぞれのacceptメソッドでVisitorのvisitメソッドを呼び出す。
class Circle implements ShapeAcceptor {
    @Override
    public void accept(ShapeVisitor visitor) {
        visitor.visit(this);
    }
}

class Rectangle implements ShapeAcceptor {
    @Override
    public void accept(ShapeVisitor visitor) {
        visitor.visit(this);
    }
}

3. Visitorインターフェース

  • ShapeVisitorインターフェース
    ShapeVisitorインターフェースは、各図形に対するvisitメソッドを定義している。
interface ShapeVisitor {
    void visit(Circle circle);
    void visit(Rectangle rectangle);
}

4. 具体的なVisitorクラス

  • DrawingVisitorクラス:
    DrawingVisitorは、図形を描画するための具体的なVisitorで、各図形に対して異なる描画処理を実装している。
class DrawingVisitor implements ShapeVisitor {
    @Override
    public void visit(Circle circle) {
        System.out.println("Drawing a Circle");
    }

    @Override
    public void visit(Rectangle rectangle) {
        System.out.println("Drawing a Rectangle");
    }
}

5. 使用例

  • VisitorPatternExampleクラスで、図形を作成し、DrawingVisitorを使って描画処理を行う例。
public class VisitorPatternExample {
    public static void main(String[] args) {
        ShapeAcceptor circle = new Circle();
        ShapeAcceptor rectangle = new Rectangle();

        ShapeVisitor drawingVisitor = new DrawingVisitor();

        circle.accept(drawingVisitor);   // "Drawing a Circle"
        rectangle.accept(drawingVisitor); // "Drawing a Rectangle"
    }
}

ダブルディスパッチの模倣

  • CircleRectangleacceptメソッド内で、Visitorvisitメソッドを呼び出している。
  • この時、具体的な型(CircleRectangle)に基づいて、適切なvisitメソッドが呼び出される。これがダブルディスパッチの模倣
+---------------------------------------+
|   ShapeAcceptor                       |
|---------------------------------------|
| + accept(visitor: ShapeVisitor): void |
+---------------------------------------+
       ^
       |     +---------------------------------------+  
       +-----|      Circle                           |  
       |     |---------------------------------------|  
       |     | + accept(visitor: ShapeVisitor): void |
       |     +---------------------------------------+ 
       |
       |     +---------------------------------------+
       +-----|    Rectangle                          |
             |---------------------------------------|
             | + accept(visitor: ShapeVisitor): void |
             +---------------------------------------+
+-------------------------------------+
|   ShapeVisitor                      |
|-------------------------------------|
| + visit(circle: Circle): void       |
| + visit(rectangle: Rectangle): void |
+-------------------------------------+
          ^
          |
+-------------------------------------+
|   DrawingVisitor                    |
|-------------------------------------|
| + visit(circle: Circle): void       |
| + visit(rectangle: Rectangle): void |
+-------------------------------------+
+------------------------------+
| VisitorPatternExample        |
|------------------------------|
| + main(args: String[]): void |
+------------------------------+
+----------------+           +--------------------+
|    Circle      |           |   DrawingVisitor   |
+----------------+           +--------------------+
|                |           |                    |
| accept()       |---------->| visit(Circle)      |
|                |           |                    |
|                |<----------| "Drawing a Circle" |
|                |           |                    |
+----------------+           +--------------------+

+----------------+           +-----------------------+
|   Rectangle    |           |   DrawingVisitor      |
+----------------+           +-----------------------+
|                |           |                       |
| accept()       |---------->| visit(Rectangle)      |
|                |           |                       |
|                |<----------| "Drawing a Rectangle" |
|                |           |                       |
+----------------+           +-----------------------+

サンプル2

// Acceptorインターフェース
interface LogAcceptor {
    void accept(LogVisitor visitor);
}

// 具体的なAcceptorクラス
class InfoLog implements LogAcceptor {
    private String message;

    public InfoLog(String message) {
        this.message = message;
    }

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

    public String getMessage() {
        return message;
    }
}

class WarningLog implements LogAcceptor {
    private String message;

    public WarningLog(String message) {
        this.message = message;
    }

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

    public String getMessage() {
        return message;
    }
}

class ErrorLog implements LogAcceptor {
    private String message;

    public ErrorLog(String message) {
        this.message = message;
    }

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

    public String getMessage() {
        return message;
    }
}

// Visitorインターフェース
interface LogVisitor {
    void visit(InfoLog infoLog);
    void visit(WarningLog warningLog);
    void visit(ErrorLog errorLog);
}

// 具体的なVisitorクラス
class ConsoleLogVisitor implements LogVisitor {
    @Override
    public void visit(InfoLog infoLog) {
        System.out.println("INFO: " + infoLog.getMessage());
    }

    @Override
    public void visit(WarningLog warningLog) {
        System.out.println("WARNING: " + warningLog.getMessage());
    }

    @Override
    public void visit(ErrorLog errorLog) {
        System.out.println("ERROR: " + errorLog.getMessage());
    }
}

// メインクラス
public class VisitorPatternLogExample {
    public static void main(String[] args) {
        LogAcceptor infoLog = new InfoLog("This is an info message.");
        LogAcceptor warningLog = new WarningLog("This is a warning message.");
        LogAcceptor errorLog = new ErrorLog("This is an error message.");

        LogVisitor consoleLogVisitor = new ConsoleLogVisitor();

        infoLog.accept(consoleLogVisitor);   // "INFO: This is an info message."
        warningLog.accept(consoleLogVisitor); // "WARNING: This is a warning message."
        errorLog.accept(consoleLogVisitor);   // "ERROR: This is an error message."
    }
}
  1. LogAcceptorインターフェース:
    このメソッは、Visitorを受け入れるために使用される。
    accept(LogVisitor visitor)メソッドを持ち、Visitorを受け入れる役割を果たしている。

  2. 具体的なAcceptorクラス:

    • InfoLog: 情報ログメッセージを表す。
    • WarningLog: 警告ログメッセージを表す。
    • ErrorLog: エラーログメッセージを表す。

各クラスは、LogAcceptorインターフェースを実装し、acceptメソッドをオーバーライドしている。

  1. LogVisitorインターフェース:
    各ログメッセージの種類に対するvisitメソッドを持っている。

  2. ConsoleLogVisitorクラス:
    LogVisitorインターフェースを実装し、各ログメッセージの処理方法を定義している。
    ここでは、コンソールにメッセージを出力するという処理方法を定義している。

  3. VisitorPatternLogExampleクラス:
    メインメソッドを持ち、ログメッセージを作成し、Visitorを使用してそれらを処理する。

+-------------------+          +---------------------+
|   LogAcceptor     |<>--------|     LogVisitor      |
+-------------------+          +---------------------+
| + accept(visitor) |          | + visit(infoLog)    |
+-------------------+          | + visit(warningLog) |
                               | + visit(errorLog)   |
                               +---------------------+
                                      ^
                                      |
                                      |
                +---------------------+---------------------+
                |                     |                     |
        +----------------+   +----------------+   +----------------+
        |    InfoLog     |   |   WarningLog   |   |    ErrorLog    |
        +----------------+   +----------------+   +----------------+
        | - message      |   | - message      |   | - message      |
        | + accept()     |   | + accept()     |   | + accept()     |
        | + getMessage() |   | + getMessage() |   | + getMessage() |
        +----------------+   +----------------+   +----------------+
+----------------+          +---------------------+
|   InfoLog      |          | ConsoleLogVisitor   |
+----------------+          +---------------------+
|                |          |                     |
| accept()       |----------> visit(InfoLog)      |
|                |          |                     |
|                |<---------|  "INFO: ..."        |
+----------------+          +---------------------+

+----------------+          +---------------------+
| WarningLog     |          | ConsoleLogVisitor   |
+----------------+          +---------------------+
|                |          |                     |
| accept()       |----------> visit(WarningLog)   |
|                |          |                     |
|                |<---------|  "WARNING: ..."     |
+----------------+          +---------------------+

+----------------+          +---------------------+
| ErrorLog       |          | ConsoleLogVisitor   |
+----------------+          +---------------------+
|                |          |                     |
| accept()       |----------> visit(ErrorLog)     |
|                |          |                     |
|                |<---------|  "ERROR: ..."       |
+----------------+          +---------------------+

Discussion