Visitorパターン

# はじめに
GoFデザインパターンの一つで,振る舞いに関するパターンである
**Visitorパターン**について見ていきます.

# Visitorパターンについて
Visitorパターンは,**既存の階層構造に新しい操作を追加**することができます.

新しい操作は,Visitorクラスに置かれます.

既存の階層構造に属するクラスが,Visitorクラスのメソッドを受け入れることで
新しい操作を実現します.

# Visitorパターンの特徴
Visitorパターンの特徴は以下のようになります.

##### 操作の追加の容易性
Visitorクラスに操作を追加するためです.

##### 既存の階層構造の変更するような仕様は不向き
新しい派生クラスが追加することでVisitorクラスも変更するためです.

# Visitorパターンの構造
[Visitorパターンとは｜GoFデザインパターンの解説](https://cs-techblog.com/technical/visitor-pattern/#:~:text=Visitor%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AF%E3%80%81%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%A7%8B%E9%80%A0,%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82)によると,Visitorパターンの
基本構造は以下のようになります.

> - Visitor: 各要素に適用される操作を定義するインターフェース
> - ConcreteVisitor: Visitorインターフェースを実装した具体的なクラス
> - Element: Visitorを受け入れる要素のインターフェース
> - ConcreteElement: Elementインターフェースを実装した具体的な要素クラス

クラス図は以下のようになります.
#### クラス図
![スクリーンショット 2025-01-09 145109.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/b3bbcc2b-651c-e639-b645-0fb0c7c74243.png)


# Visitorパターンの実装
ネット販売システムを例にして考えてみます.
(題材は適当にGPTに投げてみました)

以下は商品やサービスの購入を題材にしたクラス図とソースコードとなります.
(ソースコードもGPTで作成したものです)

#### クラス図
![スクリーンショット 2025-01-09 152124.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586226/1a2ebf68-5648-7d63-3aaf-8b7fd7cabdc6.png)


#### ソースコード
```java:OrderVisitor.java
public interface OrderVisitor {
    void visit(Product product);
    void visit(Service service);
}
```

```java:PriceCalculatorVisitor.java
public class PriceCalculatorVisitor implements OrderVisitor {
    private double totalPrice = 0.0;

    @Override
    public void visit(Product product) {
        totalPrice += product.getPrice() * product.getQuantity();
    }

    @Override
    public void visit(Service service) {
        totalPrice += service.getPrice();
    }

    public double getTotalPrice() {
        return totalPrice;
    }
}
```

```java:ShippingCostCalculatorVisitor.java
public class ShippingCostCalculatorVisitor implements OrderVisitor {
    private double totalShippingCost = 0.0;

    @Override
    public void visit(Product product) {
        totalShippingCost += product.getWeight() * 0.5; // 重量1kgあたり0.5ドル
    }

    @Override
    public void visit(Service service) {
        // サービスは配送コストなし
    }

    public double getTotalShippingCost() {
        return totalShippingCost;
    }
}
```

```java:OrderItem.java
public interface OrderItem {
    void accept(OrderVisitor visitor);
}
```

```java:Product.java
public class Product implements OrderItem {
    private String name;
    private double price;
    private int quantity;
    private double weight;

    public Product(String name, double price, int quantity, double weight) {
        this.name = name;
        this.price = price;
        this.quantity = quantity;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    public int getQuantity() {
        return quantity;
    }

    public double getWeight() {
        return weight;
    }

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

```java:Service.java
public class Service implements OrderItem {
    private String description;
    private double price;

    public Service(String description, double price) {
        this.description = description;
        this.price = price;
    }

    public String getDescription() {
        return description;
    }

    public double getPrice() {
        return price;
    }

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

```java:Main.java
public class Main {
    public static void main(String[] args) {
        // 注文アイテムを作成
        OrderItem product1 = new Product("Laptop", 1200.0, 1, 3.0);
        OrderItem product2 = new Product("Phone", 800.0, 2, 1.0);
        OrderItem service1 = new Service("Extended Warranty", 200.0);

        // 注文リストを作成
        OrderItem[] orderItems = {product1, product2, service1};

        // 合計価格を計算
        PriceCalculatorVisitor priceCalculator = new PriceCalculatorVisitor();
        for (OrderItem item : orderItems) {
            item.accept(priceCalculator);
        }
        System.out.println("Total Price: $" + priceCalculator.getTotalPrice());

        // 配送コストを計算
        ShippingCostCalculatorVisitor shippingCostCalculator = new ShippingCostCalculatorVisitor();
        for (OrderItem item : orderItems) {
            item.accept(shippingCostCalculator);
        }
        System.out.println("Total Shipping Cost: $" + shippingCostCalculator.getTotalShippingCost());
    }
}
```

各クラス概要は以下の通りです

##### 1.OrderVisitor(Visitor)
各種類の注文アイテムに対して異なる処理を提供します.


##### 2.PriceCalculatorVisitor(Concrete Visitor)
全ての注文アイテムの合計価格を計算します.

##### 3.ShippingCostCalculatorVisitor(Concrete Visitor)
全ての商品の配送コストを計算します.


##### 4.OrderItem(Element)
商品やサービスなど,Visitorによる処理を受け入れるためのインタフェースです.

受け取ったVisitorインスタンスに自分自身を渡し,適切なvisitメソッドを呼びます.

##### 5.Product(Concrete Element)
商品を表現したクラスです.

##### 6.Service(Concrete Element)
サービスを表現したクラスです.

## メリット
Visitorパターンを適用したことによるメリットを見ていきます.

#### 新しい処理の追加
例えば,割引計算や税金計算といった新しい処理を追加する場合を考えます.

このとき,Visitorパターンを用いていない場合は商品やサービスのクラス内にメソッドを
それぞれ追加する必要があるでしょう.

しかし,Visitorパターンで実装することにより**新しいクラスをVisitorとして追加**することで
新しい処理の追加を容易に実現できます.

####

## 問題点
Visitorパターンを適用したことによる問題点を見ていきます.

#### 新しい要素の追加
例えば,新たにServiceやProduct以外の新しい要素を追加する場合を考えます.

このとき,Visitorパターンで実装していることにより,新しい要素を追加するたびVisitorクラス,
ConcreteVisitorクラスに新たなvisitメソッドを追加する必要があります.

このように,新しい要素を追加することが予想される場合にVisitorパターンを実装すると
それだけ**Visitorを変更する手間が発生**します.

# 参考
https://cs-techblog.com/technical/visitor-pattern/#:~:text=Visitor%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AF%E3%80%81%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%A7%8B%E9%80%A0,%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82