CrackedRuby CrackedRuby

Overview

The Memento Pattern addresses the problem of capturing an object's state for later restoration without exposing its internal structure or violating encapsulation principles. The pattern establishes a clear separation between the object being saved (originator), the saved state itself (memento), and the entity managing save operations (caretaker).

The pattern emerged from the need to implement undo mechanisms, checkpoints, and state rollback features in applications. Traditional approaches to saving object state often required either making internal state public or creating tight coupling between objects and their storage mechanisms. The Memento Pattern solves this by encapsulating state capture within the originator object itself, producing an opaque memento object that other components can store and return without understanding its contents.

Three primary participants define the pattern structure. The Originator creates mementos containing snapshots of its current state and uses mementos to restore previous states. The Memento stores the originator's internal state and protects against access by objects other than the originator. The Caretaker manages memento storage, determining when to capture states and when to restore them, but never examines or modifies memento contents.

The pattern's value appears most clearly in situations requiring state history tracking, transactional operations with rollback capability, or complex undo/redo systems. Applications range from text editors maintaining edit history to game engines implementing save points, from database transaction systems to configuration management tools.

Consider a text editor example where users need undo functionality:

class TextEditor
  attr_reader :content
  
  def initialize
    @content = ""
  end
  
  def write(text)
    @content += text
  end
  
  def save
    EditorMemento.new(@content)
  end
  
  def restore(memento)
    @content = memento.state
  end
end

class EditorMemento
  attr_reader :state
  
  def initialize(state)
    @state = state
  end
end

The pattern maintains clear boundaries between concerns: the editor manages content, mementos store state snapshots, and a separate history manager controls save/restore timing without accessing internal editor details.

Key Principles

The Memento Pattern operates on three fundamental principles: encapsulation preservation, state externalization, and separation of concerns. Each principle addresses specific challenges in state management while maintaining object-oriented design integrity.

Encapsulation Preservation ensures that saving and restoring object state does not expose internal implementation details. The originator produces mementos through controlled interfaces, placing state data inside memento objects without making that data accessible to external callers. Only the originator can extract state from its own mementos. This principle prevents the fragile couplings that arise when external code depends on internal state representations.

The originator controls what state gets saved and how. It might save complete state or selective subsets depending on requirements. The memento acts as an opaque token to external code:

class DatabaseConnection
  def initialize(host, port, credentials)
    @host = host
    @port = port
    @credentials = credentials
    @transaction_state = nil
  end
  
  def create_memento
    ConnectionMemento.new(@host, @port, @credentials, @transaction_state)
  end
  
  def restore_memento(memento)
    @host = memento.host
    @port = memento.port
    @credentials = memento.credentials
    @transaction_state = memento.transaction_state
  end
  
  private
  
  class ConnectionMemento
    attr_reader :host, :port, :credentials, :transaction_state
    
    def initialize(host, port, credentials, transaction_state)
      @host = host
      @port = port
      @credentials = credentials
      @transaction_state = transaction_state
    end
  end
end

External code receives ConnectionMemento instances but cannot access their internal attributes directly due to the private class definition. Only the originating DatabaseConnection can use these mementos.

State Externalization moves state storage outside the originator object. The originator creates self-contained memento objects that exist independently in memory. This externalization enables multiple snapshots to coexist, supports state transfer between object instances, and facilitates serialization of captured states.

Mementos represent immutable state snapshots. Once created, memento contents do not change. This immutability prevents accidental state corruption and ensures that restoring a memento produces predictable results. If state needs updating, the originator creates a new memento rather than modifying existing ones:

class ShoppingCart
  def initialize
    @items = []
    @discounts = []
  end
  
  def add_item(item)
    @items << item
  end
  
  def apply_discount(discount)
    @discounts << discount
  end
  
  def checkpoint
    CartMemento.new(@items.dup, @discounts.dup)
  end
  
  def restore(memento)
    @items = memento.items.dup
    @discounts = memento.discounts.dup
  end
end

class CartMemento
  attr_reader :items, :discounts
  
  def initialize(items, discounts)
    @items = items.freeze
    @discounts = discounts.freeze
  end
end

The checkpoint method creates independent copies of internal state, and the memento freezes these copies to enforce immutability. Restoration creates new copies rather than reusing references, preventing shared state mutations.

Separation of Concerns divides responsibilities across three distinct roles. The originator focuses on its core functionality and state management. Mementos serve as passive state containers with no behavior beyond storage. Caretakers handle memento lifecycle without understanding state contents.

This separation enables flexible storage strategies. A caretaker might maintain a simple history stack, implement sophisticated undo/redo trees, persist mementos to disk, or transfer them across network boundaries. The originator remains unaware of these storage details:

class CommandHistory
  def initialize
    @history = []
    @current_position = -1
  end
  
  def save(memento)
    # Remove any states after current position (redo history)
    @history = @history[0..@current_position]
    @history << memento
    @current_position += 1
  end
  
  def undo
    return nil if @current_position <= 0
    @current_position -= 1
    @history[@current_position]
  end
  
  def redo
    return nil if @current_position >= @history.length - 1
    @current_position += 1
    @history[@current_position]
  end
end

The CommandHistory caretaker implements undo/redo logic without knowing what states it manages. It treats mementos as opaque objects, focusing solely on their organization and retrieval.

Design Considerations

The Memento Pattern suits specific scenarios while presenting challenges in others. Selection requires analyzing state complexity, frequency of state capture, and memory constraints.

State Complexity and Granularity determine whether the pattern provides value. Objects with simple state (few primitive attributes) might not benefit from the pattern's overhead. Objects with complex state graphs, numerous interconnected components, or intricate internal structures gain more value from formalized state capture. The pattern excels when state representation differs significantly from external interfaces.

Consider an object graph where state includes relationships between multiple entities. Direct state access would require understanding these relationships. The Memento Pattern lets the originator manage relationship serialization internally:

class WorkflowEngine
  def initialize
    @steps = []
    @transitions = {}
    @current_step = nil
    @context_data = {}
  end
  
  def create_checkpoint
    WorkflowMemento.new(
      steps: @steps.map(&:dup),
      transitions: @transitions.dup,
      current_step: @current_step,
      context: deep_copy(@context_data)
    )
  end
  
  def restore_checkpoint(memento)
    @steps = memento.steps
    @transitions = memento.transitions
    @current_step = memento.current_step
    @context_data = memento.context
  end
  
  private
  
  def deep_copy(obj)
    Marshal.load(Marshal.dump(obj))
  end
end

The workflow engine maintains complex internal state that external code should not manipulate directly. The memento encapsulates this complexity.

Frequency of State Capture impacts performance and memory usage. Applications requiring frequent snapshots (every keystroke in a text editor) need efficient memento implementations. Infrequent captures (game save points) tolerate more overhead. High-frequency scenarios might use incremental mementos storing only state deltas rather than complete snapshots.

Memory consumption multiplies with snapshot count. An application saving state every second accumulates 3,600 mementos per hour. If each memento consumes 1KB, hourly memory usage reaches 3.6MB. Applications must implement memento pruning strategies:

class MemoryEfficientCaretaker
  def initialize(max_history: 50)
    @max_history = max_history
    @mementos = []
    @pruned_count = 0
  end
  
  def save(memento)
    @mementos << memento
    
    if @mementos.size > @max_history
      @mementos.shift
      @pruned_count += 1
    end
  end
  
  def stats
    {
      current_history: @mementos.size,
      pruned_total: @pruned_count,
      oldest_timestamp: @mementos.first&.timestamp
    }
  end
end

Memory limits force trade-offs between history depth and memory consumption. Applications might combine recent fine-grained snapshots with older coarse-grained snapshots.

Encapsulation vs. Performance creates tension in pattern implementation. Strict encapsulation requires mementos to be completely opaque, forcing deep copies of all state. Performance optimization might suggest sharing immutable state or using references to unchanged portions. This trade-off becomes critical for objects with large state spaces.

Ruby's object model provides flexibility in managing this trade-off. Private classes create strong encapsulation. Friend-like access patterns using module inclusion offer controlled flexibility. Freezing objects prevents mutations while enabling reference sharing:

class DataProcessor
  def initialize(config)
    @config = config  # Large configuration object
    @working_data = []
    @metadata = {}
  end
  
  def snapshot
    ProcessorMemento.new(
      config: @config,  # Share reference (config is immutable)
      working_data: @working_data.dup,  # Copy (mutable)
      metadata: @metadata.dup  # Copy (mutable)
    )
  end
end

The snapshot shares the configuration reference since that data never changes, reducing memory overhead. Mutable working data receives full copies, maintaining state independence.

Alternative Patterns address similar problems with different trade-offs. The Command Pattern with execute/undo methods encapsulates state changes rather than states themselves. This approach works well for operations with simple inverse actions but struggles with complex state or operations without clear inverses.

Serialization mechanisms like JSON or YAML export provide external state representation but sacrifice encapsulation by exposing internal structure. Version control systems offer sophisticated state management but require external infrastructure.

The Strategy Pattern combined with state management provides flexibility when different save/restore strategies suit different contexts. The pattern selection depends on whether states require complex capture logic or simple serialization suffices.

Ruby Implementation

Ruby's object model and metaprogramming capabilities support multiple memento implementation approaches. The language's flexible access control, first-class modules, and duck typing influence pattern design.

Basic Implementation with Private Classes uses nested class definitions to restrict memento access:

class GameCharacter
  attr_reader :name, :level
  
  def initialize(name)
    @name = name
    @level = 1
    @health = 100
    @mana = 50
    @inventory = []
    @position = { x: 0, y: 0 }
  end
  
  def gain_experience(points)
    @level += points / 100
  end
  
  def take_damage(amount)
    @health -= amount
  end
  
  def move(x, y)
    @position = { x: x, y: y }
  end
  
  def save_state
    CharacterMemento.new(
      level: @level,
      health: @health,
      mana: @mana,
      inventory: @inventory.dup,
      position: @position.dup
    )
  end
  
  def restore_state(memento)
    @level = memento.level
    @health = memento.health
    @mana = memento.mana
    @inventory = memento.inventory.dup
    @position = memento.position.dup
  end
  
  class CharacterMemento
    attr_reader :level, :health, :mana, :inventory, :position
    
    def initialize(level:, health:, mana:, inventory:, position:)
      @level = level
      @health = health
      @mana = mana
      @inventory = inventory.freeze
      @position = position.freeze
      @timestamp = Time.now
    end
    
    def age
      Time.now - @timestamp
    end
  end
  
  private_constant :CharacterMemento
end

The private_constant declaration prevents external code from directly instantiating CharacterMemento while allowing GameCharacter to create and use mementos. External code treats mementos as opaque objects.

Module-Based Access Control creates friend-like relationships using Ruby modules:

module MementoProtocol
  class StateSnapshot
    def initialize(data, originator_class)
      @data = data
      @originator_class = originator_class
      @created_at = Time.now
    end
    
    def restore_to(originator)
      unless originator.is_a?(@originator_class)
        raise TypeError, "Cannot restore to different class"
      end
      
      originator.restore_from_snapshot(@data)
    end
    
    def metadata
      {
        originator: @originator_class.name,
        created: @created_at,
        age: Time.now - @created_at
      }
    end
    
    protected
    
    attr_reader :data
  end
end

class FormState
  include MementoProtocol
  
  def initialize
    @fields = {}
    @validations = {}
  end
  
  def set_field(name, value)
    @fields[name] = value
  end
  
  def create_snapshot
    StateSnapshot.new(
      {
        fields: @fields.dup,
        validations: @validations.dup
      },
      self.class
    )
  end
  
  protected
  
  def restore_from_snapshot(data)
    @fields = data[:fields].dup
    @validations = data[:validations].dup
  end
end

The module defines shared memento infrastructure. Protected methods establish controlled access patterns between originators and their mementos.

Marshal-Based Deep Copying handles complex object graphs automatically:

class DocumentEditor
  def initialize
    @paragraphs = []
    @styles = {}
    @metadata = {}
    @undo_stack = []
  end
  
  def save_checkpoint
    state_data = {
      paragraphs: @paragraphs,
      styles: @styles,
      metadata: @metadata
    }
    
    DocumentMemento.new(Marshal.dump(state_data))
  end
  
  def restore_checkpoint(memento)
    state_data = Marshal.load(memento.serialized_state)
    @paragraphs = state_data[:paragraphs]
    @styles = state_data[:styles]
    @metadata = state_data[:metadata]
  end
  
  class DocumentMemento
    attr_reader :serialized_state
    
    def initialize(serialized_state)
      @serialized_state = serialized_state
      @timestamp = Time.now
    end
    
    def byte_size
      @serialized_state.bytesize
    end
  end
end

Marshal serialization creates complete copies including nested objects. This approach handles circular references and complex object graphs but requires all objects to be Marshal-compatible.

Lazy State Capture defers full state copying until necessary:

class DatabaseTransaction
  def initialize
    @original_records = {}
    @modified_records = {}
    @deleted_ids = []
  end
  
  def modify(id, attributes)
    capture_original(id) unless @original_records.key?(id)
    @modified_records[id] = attributes
  end
  
  def delete(id)
    capture_original(id) unless @original_records.key?(id)
    @deleted_ids << id
  end
  
  def create_savepoint
    TransactionMemento.new(
      originals: @original_records.dup,
      modified: @modified_records.dup,
      deleted: @deleted_ids.dup
    )
  end
  
  def rollback_to(memento)
    memento.originals.each do |id, record|
      restore_record(id, record)
    end
    
    @modified_records = {}
    @deleted_ids = []
  end
  
  private
  
  def capture_original(id)
    @original_records[id] = fetch_current_state(id)
  end
  
  def fetch_current_state(id)
    # Retrieve current record state
    { id: id, data: "current_state" }
  end
  
  def restore_record(id, record)
    # Restore record to saved state
  end
  
  class TransactionMemento
    attr_reader :originals, :modified, :deleted
    
    def initialize(originals:, modified:, deleted:)
      @originals = originals
      @modified = modified
      @deleted = deleted
    end
  end
end

Lazy capture minimizes overhead by saving only changed state. The transaction captures original values when modifications occur, not preemptively.

Struct-Based Lightweight Mementos provide efficient implementations for simple state:

class Counter
  def initialize
    @count = 0
    @increment_history = []
  end
  
  def increment(amount = 1)
    @count += amount
    @increment_history << amount
  end
  
  def snapshot
    CounterSnapshot.new(@count, @increment_history.dup)
  end
  
  def restore(snapshot)
    @count = snapshot.count
    @increment_history = snapshot.history.dup
  end
  
  CounterSnapshot = Struct.new(:count, :history) do
    def total_increments
      history.sum
    end
    
    def increment_count
      history.size
    end
  end
end

Struct provides lightweight value objects with named attributes. This approach suits simple state without complex behavior requirements.

Practical Examples

Text Editor with Undo/Redo demonstrates multi-level state management:

class TextDocument
  attr_reader :content
  
  def initialize
    @content = ""
    @cursor_position = 0
    @selection = nil
  end
  
  def insert_text(text)
    @content.insert(@cursor_position, text)
    @cursor_position += text.length
  end
  
  def delete_selection(start_pos, end_pos)
    @content[start_pos...end_pos] = ""
    @cursor_position = start_pos
  end
  
  def move_cursor(position)
    @cursor_position = [0, [position, @content.length].min].max
  end
  
  def create_memento
    DocumentMemento.new(
      content: @content.dup,
      cursor: @cursor_position,
      selection: @selection&.dup
    )
  end
  
  def restore_from_memento(memento)
    @content = memento.content.dup
    @cursor_position = memento.cursor
    @selection = memento.selection&.dup
  end
  
  class DocumentMemento
    attr_reader :content, :cursor, :selection
    
    def initialize(content:, cursor:, selection:)
      @content = content
      @cursor = cursor
      @selection = selection
      @timestamp = Time.now
    end
  end
end

class UndoRedoManager
  def initialize(document)
    @document = document
    @history = [document.create_memento]
    @current_index = 0
  end
  
  def execute_command
    # Save state before executing command
    yield
    
    # Remove any states after current position
    @history = @history[0..@current_index]
    
    # Add new state
    @history << @document.create_memento
    @current_index = @history.length - 1
  end
  
  def undo
    return false if @current_index <= 0
    
    @current_index -= 1
    @document.restore_from_memento(@history[@current_index])
    true
  end
  
  def redo
    return false if @current_index >= @history.length - 1
    
    @current_index += 1
    @document.restore_from_memento(@history[@current_index])
    true
  end
  
  def can_undo?
    @current_index > 0
  end
  
  def can_redo?
    @current_index < @history.length - 1
  end
end

# Usage
document = TextDocument.new
manager = UndoRedoManager.new(document)

manager.execute_command { document.insert_text("Hello") }
manager.execute_command { document.insert_text(" World") }
manager.execute_command { document.move_cursor(5) }
manager.execute_command { document.insert_text("!") }

puts document.content  # => "Hello! World"

manager.undo
puts document.content  # => "Hello World"

manager.undo
manager.undo
puts document.content  # => "Hello"

manager.redo
puts document.content  # => "Hello World"

The manager maintains branching history, discarding redo states when new operations occur after undo. Each command execution captures state before and after changes.

Game Save System with Multiple Slots implements persistent state storage:

class GameState
  attr_reader :player_name, :current_level
  
  def initialize(player_name)
    @player_name = player_name
    @current_level = 1
    @player_stats = { health: 100, stamina: 100, gold: 0 }
    @inventory = []
    @completed_quests = []
    @game_time = 0.0
  end
  
  def advance_level
    @current_level += 1
  end
  
  def update_stats(stats)
    @player_stats.merge!(stats)
  end
  
  def add_item(item)
    @inventory << item
  end
  
  def complete_quest(quest_id)
    @completed_quests << quest_id
  end
  
  def play_time(hours)
    @game_time += hours
  end
  
  def create_save
    GameSaveMemento.new(
      player: @player_name,
      level: @current_level,
      stats: @player_stats.dup,
      inventory: @inventory.dup,
      quests: @completed_quests.dup,
      time: @game_time
    )
  end
  
  def load_save(save)
    @player_name = save.player
    @current_level = save.level
    @player_stats = save.stats.dup
    @inventory = save.inventory.dup
    @completed_quests = save.quests.dup
    @game_time = save.time
  end
  
  class GameSaveMemento
    attr_reader :player, :level, :stats, :inventory, :quests, :time
    
    def initialize(player:, level:, stats:, inventory:, quests:, time:)
      @player = player
      @level = level
      @stats = stats.freeze
      @inventory = inventory.freeze
      @quests = quests.freeze
      @time = time
      @saved_at = Time.now
    end
    
    def display_info
      {
        player: @player,
        level: @level,
        progress: "#{@quests.length} quests completed",
        playtime: "#{@time.round(1)} hours",
        saved: @saved_at.strftime("%Y-%m-%d %H:%M")
      }
    end
    
    def serialize
      Marshal.dump(self)
    end
    
    def self.deserialize(data)
      Marshal.load(data)
    end
  end
end

class SaveGameManager
  def initialize
    @save_slots = {}
  end
  
  def save_to_slot(slot_number, game_state)
    @save_slots[slot_number] = game_state.create_save
  end
  
  def load_from_slot(slot_number, game_state)
    save = @save_slots[slot_number]
    return false unless save
    
    game_state.load_save(save)
    true
  end
  
  def list_saves
    @save_slots.transform_values(&:display_info)
  end
  
  def export_save(slot_number, filename)
    save = @save_slots[slot_number]
    return false unless save
    
    File.write(filename, save.serialize)
    true
  end
  
  def import_save(slot_number, filename)
    return false unless File.exist?(filename)
    
    data = File.read(filename)
    @save_slots[slot_number] = GameState::GameSaveMemento.deserialize(data)
    true
  end
end

# Usage
game = GameState.new("Player1")
manager = SaveGameManager.new

game.advance_level
game.update_stats(gold: 500)
game.add_item("Magic Sword")
game.complete_quest("quest_001")
game.play_time(2.5)

manager.save_to_slot(1, game)

game.advance_level
game.play_time(1.0)
manager.save_to_slot(2, game)

puts manager.list_saves
# Shows both saves with different progress

manager.load_from_slot(1, game)
puts game.current_level  # => 2 (loaded earlier save)

The save system supports multiple slots, state serialization for persistence, and metadata display for slot selection. Each save captures complete game state independently.

Transactional Operations with Rollback shows memento usage in data operations:

class ShoppingCart
  def initialize
    @items = {}  # product_id => { product: obj, quantity: int }
    @discounts = []
    @shipping_info = nil
  end
  
  def add_item(product, quantity)
    if @items[product.id]
      @items[product.id][:quantity] += quantity
    else
      @items[product.id] = { product: product, quantity: quantity }
    end
  end
  
  def remove_item(product_id)
    @items.delete(product_id)
  end
  
  def apply_discount(discount)
    @discounts << discount
  end
  
  def set_shipping(info)
    @shipping_info = info
  end
  
  def total
    item_total = @items.values.sum do |item|
      item[:product].price * item[:quantity]
    end
    
    discount_amount = @discounts.sum do |discount|
      discount.calculate(item_total)
    end
    
    item_total - discount_amount
  end
  
  def begin_transaction
    CartTransaction.new(self)
  end
  
  def create_checkpoint
    CartMemento.new(
      items: deep_copy(@items),
      discounts: @discounts.dup,
      shipping: @shipping_info&.dup
    )
  end
  
  def restore_checkpoint(memento)
    @items = deep_copy(memento.items)
    @discounts = memento.discounts.dup
    @shipping_info = memento.shipping&.dup
  end
  
  private
  
  def deep_copy(obj)
    Marshal.load(Marshal.dump(obj))
  end
  
  class CartMemento
    attr_reader :items, :discounts, :shipping
    
    def initialize(items:, discounts:, shipping:)
      @items = items
      @discounts = discounts
      @shipping = shipping
      @created_at = Time.now
    end
  end
end

class CartTransaction
  def initialize(cart)
    @cart = cart
    @savepoint = cart.create_checkpoint
    @committed = false
  end
  
  def commit
    @committed = true
    @savepoint = nil
  end
  
  def rollback
    return if @committed
    @cart.restore_checkpoint(@savepoint)
  end
  
  def execute
    yield
    commit
  rescue StandardError => e
    rollback
    raise e
  end
end

# Usage
Product = Struct.new(:id, :name, :price)

cart = ShoppingCart.new
cart.add_item(Product.new(1, "Book", 29.99), 1)

transaction = cart.begin_transaction
transaction.execute do
  cart.add_item(Product.new(2, "Pen", 5.99), 2)
  cart.apply_discount(Discount.new(10))  # 10% off
  
  # If any operation raises exception, cart rolls back
  raise "Payment failed" if some_condition
end
# Cart restored to pre-transaction state if exception raised

The transaction wraps cart modifications, automatically rolling back on failures. Each transaction creates an independent savepoint without affecting other operations.

Common Patterns

Incremental Memento reduces memory overhead by storing state deltas:

class DocumentEditor
  def initialize
    @full_state = { content: "", formatting: {}, metadata: {} }
    @last_snapshot = @full_state.dup
  end
  
  def modify(changes)
    @full_state.merge!(changes)
  end
  
  def create_incremental_memento
    changes = compute_delta(@last_snapshot, @full_state)
    
    if changes.size > @full_state.size * 0.5
      # Delta too large, save full state
      @last_snapshot = @full_state.dup
      FullMemento.new(@full_state.dup)
    else
      IncrementalMemento.new(changes, @last_snapshot.object_id)
    end
  end
  
  def restore(memento)
    case memento
    when FullMemento
      @full_state = memento.state.dup
      @last_snapshot = @full_state.dup
    when IncrementalMemento
      base_state = find_base_state(memento.base_id)
      @full_state = apply_delta(base_state, memento.delta)
      @last_snapshot = @full_state.dup
    end
  end
  
  private
  
  def compute_delta(old_state, new_state)
    delta = {}
    new_state.each do |key, value|
      delta[key] = value if old_state[key] != value
    end
    delta
  end
  
  def apply_delta(base, delta)
    base.merge(delta)
  end
  
  class FullMemento
    attr_reader :state
    
    def initialize(state)
      @state = state
      @id = object_id
    end
  end
  
  class IncrementalMemento
    attr_reader :delta, :base_id
    
    def initialize(delta, base_id)
      @delta = delta
      @base_id = base_id
    end
  end
end

Incremental mementos store differences from a base state. When deltas grow large, the system reverts to full snapshots. This approach balances memory efficiency with restoration complexity.

Command Pattern Integration combines commands with mementos:

class Command
  attr_reader :memento_before, :memento_after
  
  def initialize(receiver)
    @receiver = receiver
  end
  
  def execute
    @memento_before = @receiver.create_memento
    perform_action
    @memento_after = @receiver.create_memento
  end
  
  def undo
    @receiver.restore_memento(@memento_before) if @memento_before
  end
  
  def redo
    @receiver.restore_memento(@memento_after) if @memento_after
  end
  
  def perform_action
    raise NotImplementedError
  end
end

class AddTextCommand < Command
  def initialize(receiver, text, position)
    super(receiver)
    @text = text
    @position = position
  end
  
  def perform_action
    @receiver.insert(@text, @position)
  end
end

class DeleteTextCommand < Command
  def initialize(receiver, start_pos, length)
    super(receiver)
    @start_pos = start_pos
    @length = length
  end
  
  def perform_action
    @receiver.delete(@start_pos, @length)
  end
end

class CommandManager
  def initialize
    @executed_commands = []
    @current_index = -1
  end
  
  def execute(command)
    command.execute
    
    # Remove any commands after current index
    @executed_commands = @executed_commands[0..@current_index]
    @executed_commands << command
    @current_index = @executed_commands.length - 1
  end
  
  def undo
    return false if @current_index < 0
    
    @executed_commands[@current_index].undo
    @current_index -= 1
    true
  end
  
  def redo
    return false if @current_index >= @executed_commands.length - 1
    
    @current_index += 1
    @executed_commands[@current_index].redo
    true
  end
end

Commands store before/after mementos, enabling both undo (restore before-state) and redo (restore after-state) operations. This pattern works well when operations lack clean inverse functions.

Memento with Metadata attaches contextual information:

class AnnotatedMemento
  attr_reader :state, :metadata
  
  def initialize(state, metadata = {})
    @state = state
    @metadata = {
      created_at: Time.now,
      created_by: Thread.current[:user_id],
      reason: nil,
      tags: []
    }.merge(metadata)
  end
  
  def tagged?(tag)
    @metadata[:tags].include?(tag)
  end
  
  def age
    Time.now - @metadata[:created_at]
  end
  
  def created_by?(user_id)
    @metadata[:created_by] == user_id
  end
end

class ConfigurationManager
  def initialize
    @config = {}
    @snapshots = []
  end
  
  def update(key, value, reason: nil)
    @config[key] = value
  end
  
  def create_snapshot(tags: [], reason: nil)
    memento = AnnotatedMemento.new(
      @config.dup,
      tags: tags,
      reason: reason
    )
    @snapshots << memento
    memento
  end
  
  def find_snapshots(tag: nil, created_by: nil, max_age: nil)
    @snapshots.select do |snapshot|
      next false if tag && !snapshot.tagged?(tag)
      next false if created_by && !snapshot.created_by?(created_by)
      next false if max_age && snapshot.age > max_age
      true
    end
  end
  
  def restore_snapshot(memento)
    @config = memento.state.dup
  end
end

Metadata enables snapshot filtering, audit trails, and context-aware restoration. Applications can query snapshots by creator, age, tags, or custom attributes.

Copy-on-Write Memento shares immutable state:

class CopyOnWriteMemento
  def initialize(data)
    @data = data
    @ref_count = 1
  end
  
  def share
    @ref_count += 1
    self
  end
  
  def release
    @ref_count -= 1
    @data = nil if @ref_count <= 0
  end
  
  def get_data
    @data.dup
  end
end

class StateManager
  def initialize
    @current_state = {}
    @current_memento = nil
  end
  
  def modify(changes)
    # First modification after snapshot creates new memento
    if @current_memento
      @current_memento.release
      @current_memento = nil
    end
    
    @current_state.merge!(changes)
  end
  
  def snapshot
    unless @current_memento
      @current_memento = CopyOnWriteMemento.new(@current_state.dup)
    end
    
    @current_memento.share
  end
end

Copy-on-write mementos share state until modifications occur. Reference counting tracks shared mementos, releasing memory when no references remain.

Common Pitfalls

Excessive Memory Consumption occurs when applications create too many mementos without pruning strategies. Each memento duplicates originator state, and unbounded history accumulation leads to memory exhaustion:

# Problematic: Unlimited history growth
class BadHistoryManager
  def initialize(originator)
    @originator = originator
    @history = []
  end
  
  def save_state
    @history << @originator.create_memento  # Never removes old mementos
  end
  
  def restore(index)
    @originator.restore_memento(@history[index])
  end
end

# Solution: Implement history limits
class BoundedHistoryManager
  def initialize(originator, max_size: 100)
    @originator = originator
    @history = []
    @max_size = max_size
  end
  
  def save_state
    @history << @originator.create_memento
    
    # Remove oldest entries when limit exceeded
    @history.shift if @history.size > @max_size
  end
  
  def clear_before(timestamp)
    @history.reject! { |m| m.timestamp < timestamp }
  end
end

Applications requiring long history should implement strategies like: keeping recent fine-grained snapshots with older coarse-grained snapshots, compressing old mementos, or archiving infrequently accessed states to disk.

Breaking Encapsulation Through Memento Access defeats the pattern's purpose:

# Problematic: External code accesses memento internals
class LeakyMemento
  attr_accessor :data  # Should be read-only or protected
  
  def initialize(data)
    @data = data
  end
end

# External code manipulates memento
memento = originator.create_memento
memento.data[:field] = "modified"  # Violates encapsulation
originator.restore_memento(memento)  # Restores corrupted state

# Solution: Make mementos immutable and opaque
class SecureMemento
  def initialize(data)
    @data = data.freeze
  end
  
  # No public accessors
  protected
  attr_reader :data
end

Mementos should expose no internal state to external code. Only the originator class should access memento contents through protected or friend-class relationships.

Incorrect State Copying creates shared references instead of independent copies:

# Problematic: Shallow copy shares mutable references
class ShallowCopyProblem
  def initialize
    @data = { items: [] }
  end
  
  def add_item(item)
    @data[:items] << item
  end
  
  def create_memento
    BadMemento.new(@data)  # Shares reference to @data
  end
  
  def restore(memento)
    @data = memento.data  # Now shares reference with memento
  end
  
  class BadMemento
    attr_reader :data
    
    def initialize(data)
      @data = data  # Reference, not copy
    end
  end
end

# Modifying restored state affects memento
obj = ShallowCopyProblem.new
obj.add_item("A")
memento = obj.create_memento
obj.add_item("B")
obj.restore(memento)
obj.add_item("C")  # Modifies both obj and memento!

# Solution: Deep copy all mutable state
class ProperMemento
  def initialize(data)
    @data = Marshal.load(Marshal.dump(data))
  end
  
  def get_data
    Marshal.load(Marshal.dump(@data))
  end
end

Every memento operation requiring state transfer must create independent copies. Shallow copies suffice only when all state components are immutable.

Ignoring State Dependencies causes partial restoration:

# Problematic: Memento captures incomplete state
class PartialStateProblem
  def initialize
    @items = []
    @index = 0
    @filtered_view = []  # Derived from @items
  end
  
  def add(item)
    @items << item
    update_filtered_view
  end
  
  def create_memento
    # Forgot to include @filtered_view
    Memento.new(@items.dup, @index)
  end
  
  def restore(memento)
    @items, @index = memento.items, memento.index
    # @filtered_view now inconsistent!
  end
  
  private
  
  def update_filtered_view
    @filtered_view = @items.select { |i| i.active? }
  end
end

# Solution: Capture all state or rebuild derived state
class CompleteStateCapture
  def restore(memento)
    @items = memento.items
    @index = memento.index
    update_filtered_view  # Rebuild derived state
  end
end

Mementos must capture either all state (including derived values) or ensure derived state gets rebuilt during restoration. Inconsistent state leads to subtle bugs.

Thread Safety Violations occur when concurrent operations access mementos or originators:

# Problematic: Race conditions during snapshot
class UnsafeSnapshot
  def initialize
    @data = []
    @mutex = Mutex.new
  end
  
  def add(item)
    @mutex.synchronize { @data << item }
  end
  
  def create_memento
    # Reads @data without synchronization
    Memento.new(@data.dup)  # May see inconsistent state
  end
end

# Solution: Synchronize snapshot operations
class ThreadSafeSnapshot
  def create_memento
    @mutex.synchronize do
      Memento.new(@data.dup)
    end
  end
  
  def restore(memento)
    @mutex.synchronize do
      @data = memento.data.dup
    end
  end
end

Concurrent access requires synchronization around both snapshot creation and restoration. Mementos themselves should be immutable, avoiding synchronization needs for memento storage.

Performance Impact from Frequent Snapshots degrades application responsiveness:

# Problematic: Snapshot on every character typed
class ExpensiveSnapshots
  def initialize
    @document = LargeDocument.new
  end
  
  def on_key_press(char)
    snapshot = @document.create_memento  # Expensive operation
    @history.save(snapshot)
    @document.insert(char)
  end
end

# Solution: Batch snapshots or use timers
class EfficientSnapshots
  def initialize
    @document = LargeDocument.new
    @pending_changes = false
    @last_snapshot = Time.now
  end
  
  def on_key_press(char)
    @document.insert(char)
    @pending_changes = true
  end
  
  def periodic_snapshot
    return unless @pending_changes
    return if Time.now - @last_snapshot < 5  # 5 second minimum
    
    @history.save(@document.create_memento)
    @pending_changes = false
    @last_snapshot = Time.now
  end
end

Applications should balance snapshot frequency against performance impact. Strategies include batching changes, timer-based snapshots, or significance-based snapshots (capturing state only after substantial changes).

Reference

Core Components

Component Responsibility Key Methods
Originator Creates and restores from mementos create_memento, restore_memento
Memento Stores originator state snapshot initialize, accessors (protected)
Caretaker Manages memento storage/retrieval save, undo, redo

Implementation Patterns

Pattern When to Use Trade-offs
Private nested class Strong encapsulation needed Ruby-specific, requires nesting
Module-based access Shared memento behavior More complex, flexible access control
Marshal serialization Deep copying required Performance cost, requires Marshal support
Struct-based Simple immutable state Lightweight, less flexible
Lazy capture Large state spaces Complex logic, potential inconsistency

Memory Management Strategies

Strategy Description Best For
Bounded history Limit total mementos stored Fixed memory footprint applications
Time-based pruning Remove mementos older than threshold Long-running applications
Significance filtering Keep only important snapshots Applications with many minor changes
Incremental mementos Store state deltas Frequently saved large states
Copy-on-write Share immutable state Multiple snapshots of unchanged state

Access Control Approaches

Approach Mechanism Encapsulation Level
Private constant private_constant declaration Strong, class-scoped
Protected methods Ruby protected keyword Medium, subclass access
Module inclusion Shared module with protected methods Medium, module-scoped
Friendship simulation Conditional access checks Custom, runtime checks

Common State Copying Methods

Method Use Case Considerations
dup Shallow copy of simple objects Shares nested object references
clone Shallow copy preserving frozen state Similar to dup, respects frozen
Marshal.dump/load Deep copy of complex graphs Performance impact, requires Marshal support
Custom deep copy Fine-grained control Manual implementation, error-prone
Frozen references Immutable shared state Requires immutability guarantee

Memento Metadata Fields

Field Purpose Example Value
timestamp Creation time tracking Time.now
creator_id User attribution current_user.id
reason Change description "Before bulk update"
tags Categorization [:checkpoint, :autosave]
byte_size Memory tracking serialized_data.bytesize
version State version number 42

Decision Matrix

Requirement Pattern Choice Rationale
Simple undo/redo Basic memento with stack Straightforward history management
Branching history Tree-structured caretaker Supports multiple history paths
Minimal memory Incremental mementos Reduces storage overhead
Thread safety Synchronized operations Prevents race conditions
Serialization Marshal-based mementos Enables persistence
Rich queries Annotated mementos Supports history search

Ruby-Specific Considerations

Feature Implementation Notes
private_constant Hide memento class Prevents external instantiation
freeze Make memento immutable Prevents accidental modification
dup vs clone Copy semantics Choose based on frozen state needs
Marshal Serialization Standard library, deep copying
Struct Lightweight mementos Built-in value object
protected Access control Allows subclass/module access

Performance Characteristics

Operation Complexity Notes
Create memento O(n) n = size of state
Restore memento O(n) n = size of state
Store memento O(1) Assuming simple storage
Incremental create O(m) m = size of changes
Search history O(h) h = history size

Integration Patterns

Pattern Description Use Case
Command + Memento Commands store before/after states Complex undo/redo
Strategy + Memento Different save strategies Adaptive optimization
Observer + Memento Notify on state capture Audit logging
Prototype + Memento Clone with state Object replication