Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

RubyでMementoパターン/擬似クリップボード機能で理解

概要

GoFデザインパターンのMementoパターンについて。
ある時点でのスナップショットを残すことによって
undo,redo,snapshot,history
などの機能を実現します。

Memento(=記念品)にはwide interfaceとnarrow interfaceを用意します。
wide interfaceはprotectedで定義し、MementoとOriginatorで共有する情報を定義します。
narrow interfaceはpublicで定義し、Caretakerからも参照できるようにします。
これにより、カプセル化が破壊されることを防止します。

登場人物

Memento = 記念品。ある時点でのインスタンスの情報を保持する
Originator = 創始者。オリジナルとなるクラス
Caretaker = 世話人。Mementoの状態を管理する

UML


実装サンプル

サンプル概要

擬似クリップボード機能を実装します。
ユーザーが入力を行うとUserInputにデータを保持します。
任意のタイミングでDummyClipboardに対して以下の操作が出来ます。

UserInputのデータを追加する
DummyClipboardからデータを取り出してUserInputに設定する(undo)
undoした状態ならredoを行うことでUserInputの状態をredo以前の状態に戻す
DummyClipboardから操作履歴の一覧を取得する

登場人物

Memento = DummyClipboard : 疑似クリップボード。記念品役。
Originator = UserInput : ユーザーの入力情報。創始者役。
Caretaker = User : 入力ユーザー。世話人役。

UML


サンプルコード

user

# encoding: Shift_JIS
require_relative './dummy_clipboard'
require_relative './user_input'

=begin rdoc
= Userクラス
=end
class User
  attr_accessor :user_input,:undo_list,:redo_list

  def initialize()
    @user_input = UserInput.new
    @undo_list = Array.new
    @redo_list = Array.new
  end
  
  def input(text)
    @user_input.input(text)
    clipboard = @user_input.get_clipboard
    @undo_list.push clipboard
  end

  def undo()
    undo_element = @undo_list.pop
    unless undo_element.nil?
      unless @undo_list.last.nil?
        @user_input.input(@undo_list.last.get_text)
        @redo_list.push undo_element
      end
    end
  end

  def redo()
    redo_element = @redo_list.pop
    unless redo_element.nil?
      @user_input.input(redo_element.get_text)
      @undo_list.push redo_element.get_text
    end
  end
  
  def history()
    @undo_list.each{|undo_element|print "#{undo_element.get_text}:"}
  end
end

user_input

# encoding: Shift_JIS
require_relative './dummy_clipboard'

=begin rdoc
= UserInputクラス
=end
class UserInput
  attr_accessor :text
  def initialize()
    @text = ""
  end

  def input(text)
    @text = text
  end

  def get_clipboard()
    DummyClipboard.new(@text)
  end
  
  def undo(dummy_clipboard_list)
    previous_input = dummy_clipboard_list.pop
    @text = previous_input
    return previous_input
  end
  
  def redo(dummy_clipboard_list,redo_list)
    previous_undo = redo_list.pop
    @text = previous_undo
    return previous_undo
  end
end

dummy_clipboard

# encoding: Shift_JIS

=begin rdoc
= DummyClipboardクラス
=end
class DummyClipboard
  attr_writer :text
  def initialize(text)
    @text = text
  end

  def get_text()
    return @text
  end
end

main

# encoding: Shift_JIS
require_relative './user'
#require_relative './user_input'
#require_relative './dummy_clipboad'

user = User.new
user.input "first"
puts "inputで入力更新+クリップボード追加:#{user.user_input.text}"
user.input "second"
puts "inputで入力更新+クリップボード追加:#{user.user_input.text}"
user.input "third"
puts "inputで入力更新+クリップボード追加:#{user.user_input.text}"
print "クリップボードのヒストリを表示:"
user.history
puts ""
user.undo
puts "undoで入力を戻す+クリップボードからポップ:#{user.user_input.text}"
user.undo
puts "undoで入力を戻す+クリップボードからポップ:#{user.user_input.text}"
user.undo
puts "undoでエラーにならないことを確認:#{user.user_input.text}"
user.redo
puts "redoで入力を再戻し+再戻し用クリップボードからポップ:#{user.user_input.text}"
user.redo
puts "redoで入力を再戻し+再戻し用クリップボードからポップ:#{user.user_input.text}"
user.redo
puts "redoでエラーにならないことを確認:#{user.user_input.text}"
出力結果
inputで入力更新+クリップボード追加:first
inputで入力更新+クリップボード追加:second
inputで入力更新+クリップボード追加:third
クリップボードのヒストリを表示:first:second:third:
undoで入力を戻す+クリップボードからポップ:second
undoで入力を戻す+クリップボードからポップ:first
undoでエラーにならないことを確認:first
redoで入力を再戻し+再戻し用クリップボードからポップ:second
redoで入力を再戻し+再戻し用クリップボードからポップ:third
redoでエラーにならないことを確認:third