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

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

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

# ■ Memento（メメント・パターン）
「Memento」という英単語は、「形見・記念」を意味します。
このパターンは、あるオブジェクトの任意の時点の状態を覚えておき(保存)、 後でその状態にオブジェクトを戻すための工夫を提供するパターンです。(カプセル化を破壊せずに、状態を元に戻せる)つまり、テキストエディタ等で実装されているような「アンドゥ」(操作をキャンセルして操作前の状態に戻す)機能を提供するためのパターンです。
注意すべきことは状態を元に戻すための必要最小限の情報(フィールド値)のみを保存すると言うことです。
[(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)](http://www.itsenka.com/contents/development/designpattern/memento.html)

## UML class and sequence diagram
![W3sDesign_Memento_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/ecf574bf-9d16-2d33-b57d-92f1dfdd8aaf.jpeg)
[(以上、ウィキペディア（Wikipedia）より引用)](https://en.wikipedia.org/wiki/Memento_pattern)

# ■ "Memento"のサンプルプログラム
実際に、Mementoパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。なお、サンプルプログラム**「フルーツを集めていくサイコロゲーム」**は、次のような動作を想定するものとします。

- このゲームは自動的に進みます
- ゲームの主人公は、サイコロを振り、サイコロの目に応じて動作が決定します
- ゲームの都度、現在の状況を表示します（所持金、所持しているフルーツ）
- ゲームの開始時点では、所持金100円からスタート
- **現時点の所持金が、保存しておいた所持金を上回った場合は、その状況（所持金と所持している"おいしいフルーツ"）を保存します**
- **現時点の所持金が、保存しておいた所持金の半分を下回った場合は、以前に保存したその状況（所持金、所持している"おいしいフルーツ"）を現在の状況として復元します**
- お金がなくなったら終了します。
- 最大100回、ゲームを繰り返します


＜サイコロの目に応じた動作＞
1. サイコロの目が<font color="blue">"1"</font>が出たとき、所持金が**100円**増えます
2. サイコロの目が<font color="red">"2"</font>が出たとき、所持金が**半分**になります（端数は、切り捨て）
3. サイコロの目が<font color="blue">"6"</font>が出たとき、**フルーツ**が貰えます
　（普通の**"フルーツ"**が貰えるが、**"おいしいフルーツ"**が貰えるか、確率は、50%です）
4. その他のサイコロの目が出た場合は、何も起こりません

```console
$ go run Main.go 
==== 0
現状:[money = 100, fruits = []]
所持金が増えました
所持金は200円になりました
      (だいぶ増えたので、現在の状態を保存しておこう)

==== 1
現状:[money = 200, fruits = []]
何も起こりませんでした
所持金は200円になりました

==== 2
現状:[money = 200, fruits = []]
何も起こりませんでした
所持金は200円になりました

==== 3
現状:[money = 200, fruits = []]
所持金が増えました
所持金は300円になりました
      (だいぶ増えたので、現在の状態を保存しておこう)

==== 4
現状:[money = 300, fruits = []]
フルーツ(リンゴ)をもらいました
所持金は300円になりました

==== 5
現状:[money = 300, fruits = [リンゴ]]
何も起こりませんでした
所持金は300円になりました


...(snip)


==== 33
現状:[money = 600, fruits = [おいしいみかん おいしいぶどう ぶどう おいしいぶどう]]
所持金が増えました
所持金は700円になりました
      (だいぶ増えたので、現在の状態を保存しておこう)

==== 34
現状:[money = 700, fruits = [おいしいみかん おいしいぶどう ぶどう おいしいぶどう]]
所持金が半分になりました
所持金は350円になりました

==== 35
現状:[money = 350, fruits = [おいしいみかん おいしいぶどう ぶどう おいしいぶどう]]
フルーツ(バナナ)をもらいました
所持金は350円になりました

==== 36
現状:[money = 350, fruits = [おいしいみかん おいしいぶどう ぶどう おいしいぶどう バナナ]]
所持金が増えました
所持金は450円になりました

==== 37
現状:[money = 450, fruits = [おいしいみかん おいしいぶどう ぶどう おいしいぶどう バナナ]]
所持金が増えました
所持金は550円になりました

==== 38
現状:[money = 550, fruits = [おいしいみかん おいしいぶどう ぶどう おいしいぶどう バナナ]]
所持金が増えました
所持金は650円になりました

==== 39
現状:[money = 650, fruits = [おいしいみかん おいしいぶどう ぶどう おいしいぶどう バナナ]]
所持金が半分になりました
所持金は325円になりました
      (だいぶ減ったので、以前の状態に復帰しよう)

==== 40
現状:[money = 700, fruits = [おいしいみかん おいしいぶどう おいしいぶどう]]
所持金が増えました
所持金は800円になりました
      (だいぶ増えたので、現在の状態を保存しておこう)

...(snip)
```
最後の方で、`Memento`パターンを使った動作が確認できました。

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

- ディレクトリ構成

```console
.
├── Main.go
└── memento
    ├── game.go
    └── memento.go
```
## (1) Originator（作成者）の役
`Originator`役は、自分の現在の状態を保存したいときに、`Memento`役を作ります。`Originator`役はまた、以前の`Memento`役を渡されると、その`Memento`役を作った時点の状態に戻る処理を行います。
サンプルプログラムでは、`Gamer`構造体が、この役を努めます。

```go:memento/game.go
package memento

import (
	"fmt"
	"math/rand"
	"strings"
	"time"
)

// Gamer is struct
type Gamer struct {
	fruitname, fruits []string
	money             int
}

// NewGamer func for initializing Game
func NewGamer(money int) *Gamer {
	return &Gamer{
		fruitname: []string{"リンゴ", "ぶどう", "バナナ", "みかん"},
		money:     money,
	}
}

// GetMoney func for fetching money in Gamer
func (g *Gamer) GetMoney() int {
	return g.money
}

// Bet func for betting
func (g *Gamer) Bet() {
	rand.Seed(time.Now().UnixNano())
	dice := rand.Intn(6) + 1
	if dice == 1 {
		g.money += 100
		fmt.Println("所持金が増えました")
	} else if dice == 2 {
		g.money /= 2
		fmt.Println("所持金が半分になりました")
	} else if dice == 6 {
		f := g.getFruit()
		fmt.Printf("フルーツ(%s)をもらいました\n", f)
		g.fruits = append(g.fruits, f)
	} else {
		fmt.Println("何も起こりませんでした")
	}
}

// CreateMemento func for creating Memento
func (g *Gamer) CreateMemento() *Memento {
	m := &Memento{money: g.money}
	for _, f := range g.fruits {
		if strings.HasPrefix(f, "おいしい") {
			m.addFruit(f)
		}
	}
	return m
}

// RestoreMemento func for restoring from Memento
func (g *Gamer) RestoreMemento(memento *Memento) {
	g.money = memento.money
	g.fruits = memento.GetFruits()
}

// Print func for printing current value in Gamer
func (g *Gamer) Print() string {
	return fmt.Sprintf("[money = %d, fruits = %s]", g.money, g.fruits)
}

func (g *Gamer) getFruit() string {
	prefix := ""
	if rand.Int()%2 == 0 {
		prefix = "おいしい"
	}
	return prefix + g.fruitname[rand.Intn(len(g.fruitname))]
}
```
## (2) Memento（記念品）の役
`Memento`役は、`Originator`役の内部情報をまとめます。`Memento`役は、`Originator`役の内部情報を持っていますが、その情報を誰にでも公開するわけではありません。
サンプルプログラムでは、`Memento`構造体が、この役を努めます。

```go:memento/memento.go
package memento

// Memento is struct
type Memento struct {
	money  int
	fruits []string
}

// GetMoney func for fetching money in Memento
func (m *Memento) GetMoney() int {
	return m.money
}

func (m *Memento) addFruit(fruit string) {
	m.fruits = append(m.fruits, fruit)
}

// GetFruits func for fetching current fruits list in Memento
func (m *Memento) GetFruits() []string {
	return m.fruits
}
```
## (3) Caretaker（世話をする人）の役
`Caretaker`役は、現在の`Originator`役の状態を保存したいときに、そのことを`Originator`役に伝えます。`Originator`役は、それを受けて`Memento`役を作り、`Caretaker`役に渡します。
`Caretaker`役は将来の必要に備えて、その`Memento`役を保存しておきます。
サンプルプログラムでは、`startMain`関数が、この役を努めます。

```go:Main.go
package main

import (
	"fmt"
	"time"

	"./memento"
)

func startMain() {
	gamer := memento.NewGamer(100)
	memento := gamer.CreateMemento()

	for i := 0; i < 100; i++ {
		fmt.Printf("==== %d\n", i)
		fmt.Printf("現状:%s\n", gamer.Print())
		gamer.Bet()
		fmt.Printf("所持金は%d円になりました\n", gamer.GetMoney())

		if gamer.GetMoney() > memento.GetMoney() {
			fmt.Println("      (だいぶ増えたので、現在の状態を保存しておこう)")
			memento = gamer.CreateMemento()
		} else if gamer.GetMoney() < memento.GetMoney()/2 {
			fmt.Println("      (だいぶ減ったので、以前の状態に復帰しよう)")
			gamer.RestoreMemento(memento)
		}

		time.Sleep(time.Second * 1)
		fmt.Println("")
	}
}

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

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