Memento を Ruby で
Memento は、 振る舞いに関するデザインパターンの一つで、 オブジェクトの状態のスナップショットを作成し、 それを将来復元します。
Memento は、 その対象オブジェクトの内部構造やスナップショットの内部に保存されるデータの機密を守ります。
複雑度:
人気度:
使用例: Memento の原則は、 シリアライゼーションを使って達成することが可能で、 Ruby ではよく見かけます。 これは、 オブジェクトの状態のスナップショットを作る上で唯一の方法でも、 最も効率のいい方法でもありませんが、 オリジネーターの構造を他のオブジェクトから隠蔽しつつ状態のバックアップを取ることはできます。
概念的な例
この例は、 Memento デザインパターンの構造を説明するためのものです。 以下の質問に答えることを目的としています:
- どういうクラスからできているか?
- それぞれのクラスの役割は?
- パターンの要素同士はどう関係しているのか?
main.rb: 概念的な例
# The Originator holds some important state that may change over time. It also # defines a method for saving the state inside a memento and another method for # restoring the state from it. class Originator # For the sake of simplicity, the originator's state is stored inside a single # variable. attr_accessor :state private :state # @param [String] state def initialize(state) @state = state puts "Originator: My initial state is: #{@state}" end # The Originator's business logic may affect its internal state. Therefore, # the client should backup the state before launching methods of the business # logic via the save() method. def do_something puts 'Originator: I\'m doing something important.' @state = generate_random_string(30) puts "Originator: and my state has changed to: #{@state}" end private def generate_random_string(length = 10) ascii_letters = [*'a'..'z', *'A'..'Z'] (0...length).map { ascii_letters.sample }.join end # Saves the current state inside a memento. def save ConcreteMemento.new(@state) end # Restores the Originator's state from a memento object. def restore(memento) @state = memento.state puts "Originator: My state has changed to: #{@state}" end end # The Memento interface provides a way to retrieve the memento's metadata, such # as creation date or name. However, it doesn't expose the Originator's state. class Memento # @abstract # # @return [String] def name raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end # @abstract # # @return [String] def date raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end end class ConcreteMemento < Memento # @param [String] state def initialize(state) @state = state @date = Time.now.strftime('%F %T') end # The Originator uses this method when restoring its state. attr_reader :state # The rest of the methods are used by the Caretaker to display metadata. def name "#{@date} / (#{@state[0, 9]}...)" end # @return [String] attr_reader :date end # The Caretaker doesn't depend on the Concrete Memento class. Therefore, it # doesn't have access to the originator's state, stored inside the memento. It # works with all mementos via the base Memento interface. class Caretaker # @param [Originator] originator def initialize(originator) @mementos = [] @originator = originator end def backup puts "\nCaretaker: Saving Originator's state..." @mementos << @originator.save end def undo return if @mementos.empty? memento = @mementos.pop puts "Caretaker: Restoring state to: #{memento.name}" begin @originator.restore(memento) rescue StandardError undo end end def show_history puts 'Caretaker: Here\'s the list of mementos:' @mementos.each { |memento| puts memento.name } end end originator = Originator.new('Super-duper-super-puper-super.') caretaker = Caretaker.new(originator) caretaker.backup originator.do_something caretaker.backup originator.do_something caretaker.backup originator.do_something puts "\n" caretaker.show_history puts "\nClient: Now, let's rollback!\n" caretaker.undo puts "\nClient: Once more!\n" caretaker.undo output.txt: 実行結果
Originator: My initial state is: Super-duper-super-puper-super. Caretaker: Saving Originator's state... Originator: I'm doing something important. Originator: and my state has changed to: CHYzYSIWbqvWkCzIHOqTyEJWfQlFMn Caretaker: Saving Originator's state... Originator: I'm doing something important. Originator: and my state has changed to: vbkhwCeAEQBpLwQLlhmpcvUnwzxVnT Caretaker: Saving Originator's state... Originator: I'm doing something important. Originator: and my state has changed to: SBWlQnAEPLsitiOQAZbGlXHZAeWBoW Caretaker: Here's the list of mementos: 2023-08-11 15:02:35 / (Super-dup...) 2023-08-11 15:02:35 / (CHYzYSIWb...) 2023-08-11 15:02:35 / (vbkhwCeAE...) Client: Now, let's rollback! Caretaker: Restoring state to: 2023-08-11 15:02:35 / (vbkhwCeAE...) Originator: My state has changed to: vbkhwCeAEQBpLwQLlhmpcvUnwzxVnT Client: Once more! Caretaker: Restoring state to: 2023-08-11 15:02:35 / (CHYzYSIWb...) Originator: My state has changed to: CHYzYSIWbqvWkCzIHOqTyEJWfQlFMn