Deep Dive into Behavioral Patterns - The Memento Pattern.

Hey software designers! Today, we’re diving into the Memento pattern. This pattern is essential for capturing and restoring an object’s internal state without violating encapsulation. Let’s explore its workings, benefits, and real-world applications with detailed examples.

What is the Memento Pattern?

The Memento pattern is a behavioral design pattern that allows you to capture and restore an object’s internal state without violating encapsulation. It is useful for implementing undo and redo operations in applications.

Real-World Scenario

Imagine you’re developing a text editor that supports undo and redo functionality. Each change to the document should be reversible, allowing the user to undo or redo changes.

The Problem

When capturing and restoring an object’s state, directly accessing its internals can lead to a tightly coupled and inflexible codebase. This approach makes it difficult to implement undo and redo functionality without exposing the internal state of the object.

Without Memento Pattern

class TextEditor
  attr_accessor :text

  def initialize
    @text = ''
    @history = []
  end

  def write(text)
    @history << @text
    @text += text
  end

  def undo
    @text = @history.pop
  end
end

editor = TextEditor.new
editor.write('Hello')
editor.write(' World')
puts editor.text # Output: Hello World

editor.undo
puts editor.text # Output: Hello

Drawbacks: The history is stored within the object, leading to a tightly coupled design.

The Solution: Memento Pattern

Using the Memento pattern, we can capture and restore an object’s state without exposing its internals, promoting flexibility and scalability.

With Memento Pattern

Step 1: Define the Memento

class Memento
  attr_reader :state

  def initialize(state)
    @state = state
  end
end

Step 2: Create the Originator

class TextEditor
  attr_accessor :text

  def initialize
    @text = ''
  end

  def write(text)
    @text += text
  end

  def create_memento
    Memento.new(@text.dup)
  end

  def restore(memento)
    @text = memento.state
  end
end

Step 3: Implement the Caretaker

class Caretaker
  def initialize
    @mementos = []
  end

  def add_memento(memento)
    @mementos << memento
  end

  def get_memento
    @mementos.pop
  end
end

Step 4: Implement Client Code

editor = TextEditor.new
caretaker = Caretaker.new

editor.write('Hello')
caretaker.add_memento(editor.create_memento)

editor.write(' World')
puts editor.text # Output: Hello World

editor.restore(caretaker.get_memento)
puts editor.text # Output: Hello

Benefits: Captures and restores an object’s state without violating encapsulation, promoting flexibility and scalability.

Real-World Benefits

Scenario: Implementing Undo and Redo

Imagine you need to implement undo and redo functionality. Using the Memento pattern, you can easily capture and restore the state of the object without exposing its internals.

Without Memento Pattern:

class TextEditor
  attr_accessor :text

  def initialize
    @text = ''
    @history = []
    @redo_stack = []
  end

  def write(text)
    @history << @text
    @text += text
  end

  def undo
    @redo_stack << @text
    @text = @history.pop
  end

  def redo
    @history << @text
    @text = @redo_stack.pop
  end
end

editor = TextEditor.new
editor.write('Hello')
editor.write(' World')
puts editor.text # Output: Hello World

editor.undo
puts editor.text # Output: Hello

editor.redo
puts editor.text # Output: Hello World

Drawbacks: Tightly coupled code that is difficult to maintain and extend.

With Memento Pattern:

class Memento
  attr_reader :state

  def initialize(state)
    @state = state
  end
end

class TextEditor
  attr_accessor :text

  def initialize
    @text = ''
  end

  def write(text)
    @text += text
  end

  def create_memento
    Memento.new(@text.dup)
  end

  def restore(memento)
    @text = memento.state
  end
end

class Caretaker
  def initialize
    @undo_stack = []
    @redo_stack = []
  end

  def add_memento(memento)
    @undo_stack << memento
    @redo_stack.clear
  end

  def undo
    @redo_stack << @undo_stack.pop
  end

  def redo
    @undo_stack << @redo_stack.pop
  end
end

editor = TextEditor.new
caretaker = Caretaker.new

editor.write('Hello')
caretaker.add_memento(editor.create_memento)

editor.write(' World')
caretaker.add_memento(editor.create_memento)
puts editor.text # Output: Hello World

caretaker.undo
editor.restore(caretaker.undo)
puts editor.text # Output: Hello

caretaker.redo
editor.restore(caretaker.redo)
puts editor.text # Output: Hello World

Benefits: Clean, maintainable code with high flexibility and extensibility.

Conclusion

The Memento pattern is a powerful tool for capturing and restoring an object’s internal state without violating encapsulation. It promotes flexibility, scalability, and maintainability in your code. By using the Memento pattern, you can easily implement undo and redo functionality without tightly coupling the code. Incorporate the Memento pattern into your design strategies to build more robust and adaptable software systems.

Stay tuned for more insights into software design principles and patterns.

Thôi Lo Code Đi Kẻo Sếp nạt!!