こんにちは、KOUKIです。
この記事では、前回に引き続き、デザインパターンの一つであるMementoパターンについて紹介します。
<目次>
前回
デザインパターンまとめ
Mementoパターンの適用
前回の復習ですが、Memento は「記念品」や「形見」を意味する英単語であり、Memento パターンは、 インスタンスのあるときの状態をスナップショットとして保存しておくことで、 その時のインスタンスの状態を復元することを可能にするもの、となります。
今回は、Bitcoinの送金プログラムを例に、Undo/Redo処理をMementoパターンで実装したいと思います。
※余談ですが、Bitcointの価格がめっちゃ下がってますよね^^;(2021/5 現在)買わなくてよかった。。
Memento構造体
最初に、Memento構造体を実装します。
1 2 3 |
type Memento struct { amount int } |
この構造体が、スナップショット(更新前のBitcoinの状態)になります。
Bitcoin構造体
次に、Bitcoin構造体を実装します。
1 2 3 4 5 |
type Bitcoin struct { amount int changes []*Memento revision int } |
changesプロパティにはMementoを格納し、revisionには、Bitcoinの状態を記録します。
このrevisionを定義することは、changesプロパティに格納されたMementoを指し示す冴えたやり方です。
詳細は、Undo/Redo処理をご覧ください。
Bitcoinコンストラクタ
Bitcoinコンストラクタを実装しましょう。
1 2 3 4 5 6 |
func NewBitcoin(amount int) *Bitcoin { b := &Bitcoin{amount: amount} // 初期状態のスナップショットを取得 b.changes = append(b.changes, &Memento{amount}) return b } |
Bitcoinのchangesパラメータに、初期化時のMemento(スナップショット)を格納しました。
Bitcoinメソッド
前回と同じように、Bitcoinメソッドを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func (b *Bitcoin) String() string { s := fmt.Sprintf( "Bitcoin Amount = $%d, revision = %d", b.amount, b.revision, ) return s } // 送金する func (b *Bitcoin) Transfor(amount int) *Memento { b.amount += amount // スナップショットを取得 m := Memento{b.amount} b.changes = append(b.changes, &m) // リビジョンをあげる b.revision++ fmt.Println(fmt.Sprintf( "Money Transfor $%d, amount is now $%d", amount, b.amount, )) return &m } |
使ってみよう
まだ、Undo/Redo処理を実装していませんが、ここまで実装したプログラムを使ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
func main() { bitcoin := NewBitcoin(100) fmt.Println(bitcoin) fmt.Println("----------------------") bitcoin.Transfor(500) bitcoin.Transfor(250) fmt.Println(bitcoin) fmt.Println("----------------------") } |
1 2 3 4 5 6 7 |
$ go run sample3/main.go Bitcoin Amount = $100, revision = 0 ---------------------- Money Transfor $500, amount is now $600 Money Transfor $250, amount is now $850 Bitcoin Amount = $850, revision = 2 ---------------------- |
OKですね。
Undo処理
Undo処理を実装します。Undo処理とは、revisionを一つ前の状態に戻すことです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// revisionを一つ前の状態に戻す func (b *Bitcoin) Undo() *Memento { if b.revision > 0 { b.revision-- m := b.changes[b.revision] fmt.Println(fmt.Sprintf( "Amount is now $%d, Undo Transfor $%d", b.amount, b.amount-m.amount, )) b.amount = m.amount return m } return nil } |
BitcointのchangesプロパティにMementoやrevisionを持たせているので、Undo処理が容易ですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func main() { fmt.Println("-----------------作成-------------------") bitcoin := NewBitcoin(100) fmt.Println(bitcoin) fmt.Println("-----------------送金-------------------") bitcoin.Transfor(500) fmt.Println(bitcoin) fmt.Println("-----------------送金-------------------") bitcoin.Transfor(250) fmt.Println(bitcoin) fmt.Println("-----------------Undo-------------------") bitcoin.Undo() fmt.Println(bitcoin) } |
1 2 3 4 5 6 7 8 9 10 11 12 |
$ go run sample3/main.go -----------------作成------------------- Bitcoin Amount = $100, revision = 0 -----------------送金------------------- Money Transfor $500, amount is now $600 Bitcoin Amount = $600, revision = 1 -----------------送金------------------- Money Transfor $250, amount is now $850 Bitcoin Amount = $850, revision = 2 -----------------Undo------------------- Amount is now $850, Undo Transfor $250 Bitcoin Amount = $600, revision = 1 |
Redo処理
最後に、Redo処理を実装します。RedoはUndoの逆で、revisionを一つ先に進めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func (b *Bitcoin) Redo() *Memento { if b.revision+1 < len(b.changes) { b.revision++ m := b.changes[b.revision] fmt.Println(fmt.Sprintf( "Amount is now $%d, Redo Transfor $%d", b.amount, m.amount-b.amount, )) b.amount = m.amount return m } return nil } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func main() { fmt.Println("-----------------作成-------------------") bitcoin := NewBitcoin(100) fmt.Println(bitcoin) fmt.Println("-----------------送金-------------------") bitcoin.Transfor(500) fmt.Println(bitcoin) fmt.Println("-----------------送金-------------------") bitcoin.Transfor(250) fmt.Println(bitcoin) fmt.Println("-----------------Undo-------------------") bitcoin.Undo() fmt.Println(bitcoin) fmt.Println("-----------------Redo-------------------") bitcoin.Redo() fmt.Println(bitcoin) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ go run sample3/main.go -----------------作成------------------- Bitcoin Amount = $100, revision = 0 -----------------送金------------------- Money Transfor $500, amount is now $600 Bitcoin Amount = $600, revision = 1 -----------------送金------------------- Money Transfor $250, amount is now $850 Bitcoin Amount = $850, revision = 2 -----------------Undo------------------- Amount is now $850, Undo Transfor $250 Bitcoin Amount = $600, revision = 1 -----------------Redo------------------- Amount is now $600, Redo Transfor $250 Bitcoin Amount = $850, revision = 2 |
OKですね。revisionとMementoをBitcoinに持たせることで、簡単にUndo/Redo処理が実装できたと思います。
まとめ
インスタンスの状態をプロパティとして残しておくやり方は、面白いですね^^
今まで、このような観点を持って、プログラミングをしたことはありませんでした。
チャンスがあれば、実践で使ってみたいですね!
それでは、また!
Go言語まとめ
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
package main import "fmt" type Memento struct { amount int } type Bitcoin struct { amount int changes []*Memento revision int } func NewBitcoin(amount int) *Bitcoin { b := &Bitcoin{amount: amount} // 初期状態のスナップショットを取得 b.changes = append(b.changes, &Memento{amount}) return b } func (b *Bitcoin) String() string { s := fmt.Sprintf( "Bitcoin Amount = $%d, revision = %d", b.amount, b.revision, ) return s } // 送金する func (b *Bitcoin) Transfor(amount int) *Memento { b.amount += amount // スナップショットを取得 m := Memento{b.amount} b.changes = append(b.changes, &m) // リビジョンをあげる b.revision++ fmt.Println(fmt.Sprintf( "Money Transfor $%d, amount is now $%d", amount, b.amount, )) return &m } // revisionを一つ前の状態に戻す func (b *Bitcoin) Undo() *Memento { if b.revision > 0 { b.revision-- m := b.changes[b.revision] fmt.Println(fmt.Sprintf( "Amount is now $%d, Undo Transfor $%d", b.amount, b.amount-m.amount, )) b.amount = m.amount return m } return nil } func (b *Bitcoin) Redo() *Memento { if b.revision+1 < len(b.changes) { b.revision++ m := b.changes[b.revision] fmt.Println(fmt.Sprintf( "Amount is now $%d, Redo Transfor $%d", b.amount, m.amount-b.amount, )) b.amount = m.amount return m } return nil } func main() { fmt.Println("-----------------作成-------------------") bitcoin := NewBitcoin(100) fmt.Println(bitcoin) fmt.Println("-----------------送金-------------------") bitcoin.Transfor(500) fmt.Println(bitcoin) fmt.Println("-----------------送金-------------------") bitcoin.Transfor(250) fmt.Println(bitcoin) fmt.Println("-----------------Undo-------------------") bitcoin.Undo() fmt.Println(bitcoin) fmt.Println("-----------------Redo-------------------") bitcoin.Redo() fmt.Println(bitcoin) } |
コメントを残す
コメントを投稿するにはログインしてください。