Golangで、デザインパターン「Visitor」を学ぶ

GoFのデザインパターンを学習する素材として、書籍「[増補改訂版Java言語で学ぶデザインパターン入門](https://www.hyuki.com/dp/)」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
[Qiita記事: "Pythonで、デザインパターン「Visitor」を学ぶ"](https://qiita.com/ttsubo/items/5fea7c0a1f9fc868dc1f)

今回は、Pythonで実装した”Visitor”のサンプルアプリをGolangで実装し直してみました。

# ■ Visitorパターン（ビジター・パターン）
Visitorパターンは、オブジェクト指向プログラミング およびソフトウェア工学 において、 アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。
基本的には Visitorパターンは一群のクラスに対して新たな仮想関数をクラス自体を変更せずに追加できるようにする。そのために、全ての仮想関数を適切に特化させた Visitor クラスを作成する。Visitorはインスタンスへの参照を入力として受け取り、ダブルディスパッチを用いて目的を達する。
Visitor は強力であるが、既存の仮想関数と比較して制限もある。各クラス内に小さなコールバックメソッドを追加する必要があり、各クラスのコールバックメソッドは新たなサブクラスで継承することができない。

## UML class and sequence diagram
![W3sDesign_Visitor_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/5fea52bf-737f-95c5-629f-c75f04ff032b.jpeg)

## UML class diagram
![Visitor_design_pattern.svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/9c862031-6e51-e198-33dd-66c6277bf0bc.png)

## □ 備忘録
書籍「増補改訂版Java言語で学ぶデザインパターン入門」の引用ですが、腹落ちしました。
>Visitorとは、「訪問者」という意味です。データ構造の中にたくさんの要素が格納されており、その各要素に対して何らかの「処理」をしていくとしましょう。このとき、その「処理」のコードはどこに書くべきでしょうか？普通に考えれば、データ構造を表しているクラスの中に書きますね。でも、もし、その「処理」が一種類とは限らなかったらどうでしょう。その場合、新しい処理が必要になるたびに、データ構造のクラスを修正しなければならなくなります。
`Visitor`パターンでは、**データ構造と処理を分離**します。そして、データ構造の中をめぐり歩く主体である「訪問者」を表すクラスを用意し、そのクラスに処理をまかせます。すると、新しい処理を追加したいときには新しい「訪問者」を作ればよいことになります。そして、データ構造の方は、戸を叩いてくる「訪問者」を受け入れてあげればよいのです。

# ■ "Visitor"のサンプルプログラム
実際に、Visitorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ちなみに、[Qiita記事「Golangで、デザインパターン「Composite」を学ぶ」](https://qiita.com/ttsubo/items/fa0fe5b19bfdf720784a)でのサンプルプログラムと挙動が同じになるので、実装を比較してみると`Visitor`パターンの理解がより深まります。

- ルートエントリの`ディレクトリ`に、`サブディレクトリ`および`ファイル`を追加してみる
- ルートエントリの`ディレクトリ`に、ユーザエントリの`ディレクトリ`を追加して、さらに、 `サブディレクトリ`および`ファイル`を追加してみる
- 敢えて、`ファイル`に、`ディレクトリ`を追加して、失敗することを確認する

```console
$ go run Main.go 
Making root entries
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/composite.py (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

Occurring Exception...
FileTreatmentException
```

# ■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Visitor

- ディレクトリ構成

```console
.
├── Main.go
└── visitor
    ├── element.go
    └── visitor.go
```

## (1) Visitor（訪問者）の役
`Visitor`役は、データ構造の具体的な要素（`ConcreteElement`役）ごとに、「xxxxを訪問した」というvisit(xxxx)メソッドを宣言します。visit(xxxx)はxxxxを処理するためのメソッドです。実際のコードは`ConcreteVisitor`役の側に書かれます。
サンプルプログラムでは、`Visitor`インタフェースが、この役を努めます。

```go:visitor/visitor.go
package visitor

import "fmt"

// Visitor is interface
type Visitor interface {
	visit(directory Entry)
}
```

## (2) ConcreteVisitor（具体的訪問者）の役
`ConcreteVisitor`役は、`Visitor`役のインタフェースを実装します。visitor(xxxx)という形のメソッドを実装し、個々の`ConcreteElement`役ごとの処理を記述します。
サンプルプログラムでは、`ListVistor`構造体が、この役を努めます。

```go:visitor/visitor.go
// ListVisitor is struct
type ListVisitor struct {
	currentdir string
}

// NewListVistor func for initializing ListVisitor
func NewListVistor() *ListVisitor {
	return &ListVisitor{
		currentdir: "",
	}
}

func (l *ListVisitor) visit(directory Entry) {
	fmt.Printf("%s/%s\n", l.currentdir, directory.toString())
	if _, ok := directory.(*Directory); ok {
		savedir := l.currentdir
		l.currentdir = fmt.Sprintf("%s/%s", l.currentdir, directory.getName())
		for _, f := range directory.getDir() {
			f.Accept(l)
		}
		l.currentdir = savedir
	}
}
```

## (3) Element（要素）の役
`Element`役は、`Visitor`役の訪問先を表す役です。訪問先を受け入れる`accept`メソッドを宣言します。`accept`メソッドの引数には`Visitor`役が渡されます。
サンプルプログラムでは、`Entry`インタフェースが、この役を努めます。

```go:visitor/element.go
package visitor

import "fmt"

// Entry is interface
type Entry interface {
	getName() string
	getSize() int
	Accept(v Visitor)
	toString() string
	getDir() []Entry
}
```

## (4) ConcreteElement（具体的要素）の役
`ConcreteElement`役は、`Element`役のインタフェースを実装する役です。
サンプルプログラムでは、`File`構造体と`Directory`構造体が、この役を努めます。

```go:visitor/element.go
// File is struct
type File struct {
	Entry
	name string
	size int
}

// NewFile func for initializing File
func NewFile(name string, size int) *File {
	return &File{
		name: name,
		size: size,
	}
}

func (f *File) getName() string {
	return f.name
}

func (f *File) getSize() int {
	return f.size
}

// Add func for adding file
func (f *File) Add(entry Entry) {
	if err := doError(); err != nil {
		fmt.Println(err)
	}
}

// Accept func for accepting something
func (f *File) Accept(v Visitor) {
	v.visit(f)
}

func (f *File) toString() string {
	return fmt.Sprintf("%s (%d)", f.getName(), f.getSize())
}
```
```go:visitor/element.go
// Directory is sturct
type Directory struct {
	name string
	dir  []Entry
}

// NewDirectory func for initializing Directory
func NewDirectory(name string) *Directory {
	return &Directory{
		name: name,
	}
}

func (d *Directory) getName() string {
	return d.name
}

func (d *Directory) getSize() int {
	size := 0
	for _, f := range d.dir {
		size += f.getSize()
	}
	return size
}

// Add func for adding directory
func (d *Directory) Add(entry Entry) {
	d.dir = append(d.dir, entry)
}

// Accept func for accepting something
func (d *Directory) Accept(v Visitor) {
	v.visit(d)
}

func (d *Directory) toString() string {
	return fmt.Sprintf("%s (%d)", d.getName(), d.getSize())
}

func (d *Directory) getDir() []Entry {
	return d.dir
}
```

## (5) ObjectStructure（オブジェクトの構造）の役
`ObjectStructure`役は、`Element`役の集合を扱う役です。`ConcreteVisitor`役が個々の`Element`役を扱えるようなメソッドを備えています。
サンプルプログラムでは、`Directory`構造体がこの役を努めます。（一人二役です）

## (6) Client（依頼人）の役
サンプルプログラムでは、`startMain`関数が、この役を努めます。

```go:Main.go
package main

import (
	"fmt"

	"./visitor"
)

func startMain() {
	fmt.Println("Making root entries")
	rootdir := visitor.NewDirectory("root")
	bindir := visitor.NewDirectory("bin")
	tmpdir := visitor.NewDirectory("tmp")
	usrdir := visitor.NewDirectory("usr")

	rootdir.Add(bindir)
	rootdir.Add(tmpdir)
	rootdir.Add(usrdir)

	bindir.Add(visitor.NewFile("vi", 10000))
	bindir.Add(visitor.NewFile("latex", 20000))
	rootdir.Accept(visitor.NewListVistor())

	fmt.Println("")

	fmt.Println("Making user entries")
	yuki := visitor.NewDirectory("yuki")
	hanako := visitor.NewDirectory("hanako")
	tomura := visitor.NewDirectory("tomura")

	usrdir.Add(yuki)
	usrdir.Add(hanako)
	usrdir.Add(tomura)

	yuki.Add(visitor.NewFile("diary.html", 100))
	yuki.Add(visitor.NewFile("composite.py", 200))
	hanako.Add(visitor.NewFile("memo.tex", 300))
	tomura.Add(visitor.NewFile("game.doc", 400))
	tomura.Add(visitor.NewFile("junk.mail", 500))
	rootdir.Accept(visitor.NewListVistor())

	fmt.Println("")
	fmt.Println("Occurring Exception...")
	tmpfile := visitor.NewFile("tmp.txt", 100)
	bindir = visitor.NewDirectory("bin")
	tmpfile.Add(bindir)
}

func main() {
	startMain()
}
```

## (7) その他
エラー時の振る舞いを追加します

```go:visitor/element.go
func doError() error {
	msg := "FileTreatmentException"
	return fmt.Errorf("%s", msg)
}
```

# ■ 参考URL
- [Pythonで、デザインパターン「Visitor」を学ぶ](https://qiita.com/ttsubo/items/5fea7c0a1f9fc868dc1f)