訪問先で適切な対応を！Visitorパターン！

### はじめに
こんにちは！現在デザインパターン絶賛学習中のGUです！今回はVisitorパターンについてまとめていきたいと思います。
Visitorパターンについて簡単に説明した後、復習がてら前回投稿したCompositeパターンと組み合わせたものを設計、実装します。
### 概要
Visitorパターンは「Visitor」というパターンの名前通り、各オブジェクトを訪問していくパターンです。そして訪問先により適切な処理を行っていきます。

オブジェクトの構造とそれに関連する操作を分離します。こうすることで新しい操作を追加する際に既存のオブジェクトのクラスを変更せずに済むようになります。

またVisitorパターンの構造的に、オブジェクトの型によってメソッドを選択できるディスパッチができるようになります。こうすることによってクラスにより処理を変えたいときにif文によって処理を変えるということをしなくてもよくなりメソッドの変更による影響範囲が少なくなります。

役割としては4つ挙げられます。

- **Visitor**
具体的な操作を定義するインターフェースまたは抽象クラス
- **ConcreteVisitor**　
具体的な操作を実装するクラス
- **Element**
Visitorを受け入れるacceptメソッドを持つインターフェースまたは抽象クラス
- **ConcreteElement**　
具体的な要素クラスで、acceptメソッドを実装します。Acceptメソッド内でVisitorの対応するメソッドが呼び出されます


### 設計
今回は「フォルダ構造内に合計でどれだけのファイルサイズが存在するか集計する」プログラムを作成してみたいと思います。


クラス図は以下のようになります。
図の上半分はCompositeパターンとなっています。ここでは割愛しますがCompositeパターンに関しては[前回の記事](https://qiita.com/GU39/items/6e99bee93d94f8459ec4)で説明していますので合わせてご覧ください。
![2024-07-31_15h36_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629646/d959db30-07f2-d49a-2fdb-fd80493fc58e.png)

実装はこちらです。

```FileOperationVisitor.cs
namespace Visitor
{
    /// <summary>
    /// ファイル操作を訪問するインターフェース(Visitor)
    /// </summary>
    internal interface FileOperationVisitor
    {
        /// <summary>
        /// ファイルシステムを訪問した時の処理
        /// </summary>
        /// <param name="fileSystem">訪問先のファイルシステム</param>
        /// <returns>ファイルサイズ</returns>
        int Visit(FileSystem fileSystem);
        /// <summary>
        /// ファイルを訪問した時の処理
        /// </summary>
        /// <param name="file">訪問先のファイル</param>
        /// <returns>ファイルサイズ</returns>
        int Visit(File file);
        /// <summary>
        /// ディレクトリを訪問した時の処理
        /// </summary>
        /// <param name="directory">訪問先のディレクトリー</param>
        /// <returns>ディレクトリ</returns>
        int Visit(Directory directory);

    }
}
```

```CalculateSizeVisitor.cs
namespace Visitor
{
	/// <summary>
	/// ファイルのサイズを計算するクラス(ConcreteVisitor)
	/// </summary>
    internal class CalculateSizeVisitor : FileOperationVisitor
    {
	    /// <inheritdoc/>
        public int Visit(FileSystem fileSystem)
        {
            return fileSystem.Accept(this);
        }

	    /// <inheritdoc/>
        public int Visit(File file)
        {
            return file.Length;
        }

	    /// <inheritdoc/>
        public int Visit(Directory directory)
        {
            // ディレクトリのサイズは、ディレクトリ内のファイルのサイズの合計
            return directory.Children.Sum(child => child.Accept(this));
        }
    }
}
```

```FileSystem.cs
namespace Visitor
{
    /// <summary>
    /// ファイルシステムのインターフェース
    /// </summary>
    internal interface FileSystem
    {
        /// <summary>
        /// ファイルパス
        /// </summary>
        public string Path
        {
            get;
        }

        /// <summary>
        /// Visitorを受け入れ時に行う処理
        /// </summary>
        /// <param name="visitor">訪問者</param>
        public int Accept(FileOperationVisitor visitor);
    }
}
```

```File.cs
namespace Visitor
{
    /// <summary>
    /// ファイルクラス(ConcreteElement)
    /// </summary>
    internal class File : FileSystem
    {
        /// <summary>
        /// ファイル情報
        /// </summary>
        private FileInfo m_FileInfo;

        /// <summary>
        /// ファイルパス
        /// </summary>
        public string Path { get;}

        /// <summary>
        /// ファイルサイズ
        /// </summary>
        public int Length
        {
            get
            {
                return (int)m_FileInfo.Length;
            }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="path">ファイルパス</param>
        public File(string path)
        {
            m_FileInfo = new FileInfo(path);
            Path = path;
        }

        /// <inheritdoc/>
        public int Accept(FileOperationVisitor visitor)
        {
            return visitor.Visit(this);
        }

    }
}
```

```Directory.cs
namespace Visitor
{
	/// <summary>
	/// ディレクトリのクラス(ConcreteElement)
	/// </summary>
    internal class Directory : FileSystem
    {
        
        /// ディレクトリ情報
        /// </summary>
        private DirectoryInfo m_DirectoryInfo;

        /// <summary>
        /// ディレクトリのパス
        /// </summary>
        public string Path { get; }

        /// <summary>
        /// 子要素
        /// </summary>
        public List<FileSystem> Children
        {
            get
            {
                List<FileSystem> children = new List<FileSystem>();
                foreach (var file in m_DirectoryInfo.GetFiles())
                {
                    children.Add(new File(file.FullName));
                }

                foreach (var directory in m_DirectoryInfo.GetDirectories())
                {
                    children.Add(new Directory(directory.FullName));
                }
                return children;
            }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="path">ディレクトリのパス</param>
        public Directory(string path)
        {
            m_DirectoryInfo = new DirectoryInfo(path);
            Path = path;
        }

        /// <summary>
        /// FileSystemInfoの情報を取得する
        /// </summary>
        public FileSystemInfo GetFileSystemInfo()
        {
            return m_DirectoryInfo;
        }

        /// <inheritdoc/>
        public int Accept(FileOperationVisitor visitor)
        {
            return visitor.Visit(this);
        }

        #endregion
    }
}
```

```Program.cs
namespace Visitor

public class Program
{
    public static void Main()
    {
        var filePath = Console.ReadLine();
        FileSystem fileSystem = new Directory(filePath);
        var visitor = new CalculateSizeVisitor();
        var size = visitor.Visit(fileSystem);
        Console.WriteLine(size);
    }
}
```



### まとめ
さておさらいがてらクイズです！Visitorパターンを採用すべきシチュエーションというのがあります。
次のうちどちらでしょうか？
- A.構造が拡張される可能性が高い
- B.処理が拡張される可能性が高い

答えはBです！

上で見られるようにElement(構造)はVisitorのVisit(this)を呼びます。
つまり**Elementが増えるとVisitorの処理を増やさないといけなくなります**よね。
逆にVisitorが増えた時にはElementには一切触らなくても済みます。


### おわりに
私のデザインパターン学習もいよいよ後半戦...引き続きデザインパターンを学習し習得していきたいと思います！同じく学習中の皆さんもお互い頑張っていきましょー！最後まで読んでいただきありがとうございました！