Goでデザインパターン (Mementoパターン)

Goデザインパターン

Mementoパターンとは

振る舞いに関するデザインパターンの一つ。オブジェクトの状態の保存・復元を実装の詳細を明かさずに行うことができる。

長所

  • 内部の実装や状態知らずに(カプセル化に違反せずに)、オブジェクトの状態のスナップショットを生成できる。
  • CareTakerに状態履歴の保持や復元操作についての実装を分離することで、Originatorのコードを簡素化することができる。

短所

  • Mementoの大量生成により、大量のメモリが必要となる場合がある。
  • 不要なMementoの廃棄には、CareTakerがOriginatorのライフサイクルを把握する必要がある。
  • 言語によってはMementoの内部状態の普遍性を担保できない。

利用場面

  • オブジェクトの状態復元のために、状態のスナップショットを生成したい場合。
    (トランザクションやUndo機能を実装した場合等)
  • オブジェクトの状態の取得・変更等が適切なカプセル化に違反する場合。
    (オブジェクト自体が自身のスナップショットを生成や状態の復元をするため、他オブジェクトはオブジェクトの状態等を知る必要がない)

クラス図

mementoパターン
  • Originator
    自身の状態のスナップショットを作成するメソッドと、スナップショットを利用して自身の状態を復元するメソッドを宣言する。
  • Concrete Originator
    Originatorインタフェースで宣言されたメソッドを実装する。
  • Memento
    Originatorの状態復元を行うメソッドを宣言する。
  • Concrete Memento
    Mementoインタフェースで宣言されたメソッドを実装する。
    自身を生成したOriginatorへの参照を保持し、自身のrestore()メソッドの呼び出し時に、自身が保持するOriginatorのスナップショットをOriginatorに渡し状態復元を依頼する。
    Originatorのスナップショットとなるため、このオブジェクトは生成後に変更ができないように実装する必要がある。
  • CareTaker
    Mementoオブジェクトのリストを保持し、Originatorへの参照をもつ。Undo機能等のOriginatorオブジェクトの状態をどの時点の状態に変更するかを実装する。

実装例

Originator

package memento

type originator interface {
	setState(state [3]([3]string))
	getState() [3]([3]string)
	backup() *memento
}

Concrete Originator

package memento

import (
	"fmt"
)

// 時間や操作によって変化するような状態を保持するオブジェクト
// その状態をMementoに保存するメソッドと、状態を復元するメソッドを実装する
type Game struct {
	isFirstPlayer bool
	state [3]([3]string)
}

func NewGame() *Game {
	row := [3]string{"-", "-", "-"}
	state := [3]([3]string){row, row, row}
	return &Game{isFirstPlayer: true, state: state}
}

func (g *Game) setState(state [3]([3]string)) {
	g.state = state
}

func (g *Game) getState() [3]([3]string) {
	return g.state
}

// 現在の状態をMementoに保存する。
func (g *Game) backup() *memento {
	var game originator = g
	var s memento = newSnapshot(&game)
	return &s
}

func (g *Game) SetMark(row, column int) error {
	if row > 3 || column > 3 {
		return fmt.Errorf("invalid index")
	}
	if g.isFirstPlayer {
		g.state[row - 1][column - 1] = "o"
	} else {
		g.state[row - 1][column - 1] = "x"
	}
	g.isFirstPlayer = !g.isFirstPlayer
	return nil
}

func (g *Game) Show() {
	for _, v := range g.state {
		fmt.Println(v)
	}
}

Memento

package memento

type memento interface {
	restore()
}

Concrete Memento

package memento

type snapshot struct {
	originator *originator
	state [3]([3]string)
}

func newSnapshot(o *originator) *snapshot {
	return &snapshot{
		originator: o,
		state: (*o).getState(),
	}
}

// ある時点の状態でOriginatorの状態を復元する。
func (s *snapshot) restore() {
	(*(s.originator)).setState(s.state)
}

Care Taker

package memento

type CareTaker struct {
	originator originator
	history []*memento
}

func NewCareTaker(originator originator) *CareTaker {
	return &CareTaker{originator: originator}
}

func (c *CareTaker) MakeBackup() {
	c.history = append(c.history, c.originator.backup())
}

func (c *CareTaker) Undo() {
	if backup := c.popHistory(); backup != nil {
		(*backup).restore()
	}
}

func (c *CareTaker) popHistory() *memento {
	if len(c.history) == 0 {
		return nil
	}
	lastIndex := len(c.history) - 1
	backup := c.history[lastIndex]
	c.history = c.history[:lastIndex]
	return backup
}

動作確認

package main

import (
	"fmt"
	"memento/memento"
)

func main() {

	game := memento.NewGame()
	caretaker := memento.NewCareTaker(game)

	game.Show()

	fmt.Println("== make backup ==")
	caretaker.MakeBackup()

	fmt.Println("== set mark at (1, 1) ==")
	game.SetMark(1, 1)

	fmt.Println("== make backup ==")
	caretaker.MakeBackup()

	game.Show()

	fmt.Println("== set mark at (3, 2) ==")
	game.SetMark(3, 2)

	game.Show()

	fmt.Println("== Undo ==")
	caretaker.Undo()

	game.Show()

	fmt.Println("== Undo ==")
	caretaker.Undo()

	game.Show()
}
>> go run .
[- - -]
[- - -]
[- - -]
== make backup ==
== set mark at (1, 1) ==
== make backup ==
[o - -]
[- - -]
[- - -]
== set mark at (3, 2) ==
[o - -]
[- - -]
[- x -]
== Undo ==
[o - -]
[- - -]
[- - -]
== Undo ==
[- - -]
[- - -]
[- - -]

コメント

タイトルとURLをコピーしました