Mementoパターン

概要

オブジェクトの状態を保存しておき、後から戻せるようにするためのパターン

Memento(保存するデータそのもの)、CareTaker(保存先)、Originator(状態の主)に分けることで結合度を下げることができる

使い方

bad

class Form {
  private name = ''
  private tel = ''

  private history: { name: string; tel: string }[] = []

  inputName(name: string) {
    this.name = name
    this.history.push({
      name: this.name,
      tel: this.tel
    })
  }
  
  inputTel(tel: string) {
    this.tel = tel
    this.history.push({
      name: this.name,
      tel: this.tel
    })
  }

  getName() {
    return this.name
  }

  getTel() {
    return this.tel
  }

  undo() {
    this.history.pop();

    const latest = this.history[this.history.length - 1]
    
    this.name = latest.name
    this.tel = latest.tel
  }
}

Form自身が以下の責務を全部持ってしまっている

  • name, telの状態を管理
  • name, telの状態を変更
  • name, telの参照
  • 過去の状態を保存
  • 過去の状態の保持
  • 過去の状態を復元

good

// Memento: 残すデータそのもの(不変)
class FormMemento {
  constructor(private readonly name: string, private readonly tel: string) {}
  getName() {
    return this.name
  }
  getTel() {
    return this.tel
  }
}

// Originator: 状態の主
class FormOriginator {
  private name = ''
  private tel = ''

  inputName(name: string) {
    this.name = name
  }
  
  inputTel(tel: string) {
    this.tel = tel
  }

  // Mementoに状態を保存する
  save(): FormMemento {
    return new FormMemento(this.name, this.tel)
  }

  // Mementoから状態を復元する
  restore(memento: FormMemento): void {
    this.name = memento.getName();
    this.tel = memento.getTel();
  }
}

// CareTaker: Mementoを保管、管理するもの
class FormCareTaker {
  private history: FormMemento[] = []

  push(memento: FormMemento) {
    this.history.push(memento)
  }
  pop() {
    return this.memento.pop()
  }
}

const form = new FormOriginator();
const history = new FormCareTaker();

form.inputName = 'taro'
form.inputTel = '09001234567'

history.push(form.save())

form.restore(history.pop())

Formに全部詰め込まれていた責務を以下のように分離

  • Memento: 状態のスナップショットを保持
  • Originator: 状態を保持(name, telを保持、管理)
  • CareTaker: 状態のスナップショットの履歴を保持

Formの構造が変化したときにも変更するべきところが限られるので変更が強い。

また、状態も切り離されているので、状態の保持方法を柔軟に変えやすい

使い所

  • 元に戻す処理が発生するとき
    • 何かを編集
    • Transaction的な処理

参考