CrackedRuby CrackedRuby

Overview

The Command Pattern transforms requests into standalone objects containing all information about the request. This transformation decouples the object that invokes the operation from the object that performs it, creating a layer of abstraction between senders and receivers.

The pattern originated from the need to issue requests without knowing the specific operation being requested or the receiver performing it. Traditional method calls create tight coupling between caller and callee, making it difficult to parameterize objects with operations, queue operations, or implement undo functionality. The Command Pattern addresses these limitations by reifying requests as first-class objects.

The pattern consists of four primary participants. The Command declares an interface for executing operations. Concrete Command classes implement the Command interface and define bindings between Receiver objects and actions. The Receiver contains the business logic that performs actual work. The Invoker stores commands and triggers their execution. The Client creates Command objects and sets their receivers.

This separation enables several capabilities not possible with direct method calls. Commands can be stored in data structures, passed as method arguments, and executed at different times. The same command can be reused with different receivers. Operations can be logged, queued, and undone. Invokers remain ignorant of concrete command classes and receiver implementation details.

# Basic command structure
class Command
  def execute
    raise NotImplementedError
  end
end

class ConcreteCommand < Command
  def initialize(receiver)
    @receiver = receiver
  end
  
  def execute
    @receiver.action
  end
end

class Receiver
  def action
    puts "Receiver performing action"
  end
end

# Usage
receiver = Receiver.new
command = ConcreteCommand.new(receiver)
command.execute
# => Receiver performing action

Key Principles

The Command Pattern operates on the principle of encapsulation. Each command encapsulates a single request as an object, packaging together the method call, the receiver object, and any parameters required for execution. This encapsulation transforms imperative requests into declarative objects.

The pattern establishes clear separation between three concerns: what operations are performed, when operations are performed, and how operations are performed. Commands define what operations to execute. Invokers determine when to execute commands. Receivers implement how operations are carried out. This separation creates flexibility points at each level.

Commands expose a uniform interface regardless of their internal complexity. The execute method provides a consistent entry point for all operations. Invokers interact exclusively with this interface, remaining decoupled from concrete command implementations. This uniformity enables treating diverse operations through a common protocol.

The pattern supports two models of command implementation. The first model stores receiver references and delegates work during execution. The second model implements operations directly within the command, eliminating separate receivers. The choice depends on whether commands represent wrappers around existing functionality or self-contained operations.

Command objects maintain state between creation and execution. Constructor parameters establish receiver bindings and capture request parameters. This state persists throughout the command lifecycle, enabling deferred execution. Commands can be created in one context and executed in another, with state bridging temporal and spatial gaps.

The pattern enables composition of commands. Macro commands aggregate multiple commands and execute them as a unit. Command sequences implement workflows. Conditional commands select between alternatives based on runtime conditions. These compositions treat individual commands and composites uniformly through the common interface.

Undo functionality requires commands to maintain execution state. Commands must either store prior state before execution or capture sufficient information to reverse operations. The pattern extends the command interface with an unexecute method that reverses effects. Invokers maintain command history stacks to support multi-level undo.

# Command with undo support
class UndoableCommand
  def execute
    raise NotImplementedError
  end
  
  def unexecute
    raise NotImplementedError
  end
end

class AddTextCommand < UndoableCommand
  def initialize(document, text)
    @document = document
    @text = text
    @position = nil
  end
  
  def execute
    @position = @document.cursor_position
    @document.insert(@text)
  end
  
  def unexecute
    @document.delete(@position, @text.length)
  end
end

The pattern transforms temporal coupling into explicit object relationships. Traditional method calls execute immediately within the caller's context. Commands separate invocation time from execution time. This temporal decoupling enables queueing, scheduling, and transaction-like operations where multiple requests are grouped and executed atomically.

Ruby Implementation

Ruby provides several features that simplify Command Pattern implementation. Blocks and Procs offer lightweight alternatives to command classes for simple cases. Lambdas provide command-like behavior with closure semantics. Method objects encapsulate method calls as first-class objects. The choice between these approaches depends on required functionality and complexity.

The simplest command implementation uses classes with execute methods. Ruby's dynamic typing eliminates the need for explicit command interfaces. Duck typing allows any object with an execute method to function as a command. This flexibility reduces boilerplate compared to statically-typed languages.

class LightOnCommand
  def initialize(light)
    @light = light
  end
  
  def execute
    @light.turn_on
  end
  
  def unexecute
    @light.turn_off
  end
end

class Light
  def turn_on
    puts "Light is on"
  end
  
  def turn_off
    puts "Light is off"
  end
end

light = Light.new
command = LightOnCommand.new(light)
command.execute
# => Light is on
command.unexecute
# => Light is off

For commands without undo requirements or complex state, Procs provide a concise alternative. The Proc captures the receiver and operation as a closure. This approach works well for simple operations that don't require explicit undo logic.

class RemoteControl
  def initialize
    @commands = {}
  end
  
  def set_command(slot, on_command, off_command)
    @commands[slot] = { on: on_command, off: off_command }
  end
  
  def press_button(slot, state)
    @commands[slot][state].call
  end
end

fan = Fan.new
remote = RemoteControl.new
remote.set_command(
  :fan,
  -> { fan.high },
  -> { fan.off }
)
remote.press_button(:fan, :on)

Ruby modules enable sharing command behavior across classes. A command module can define common functionality like logging or timing. Concrete command classes include the module to inherit shared behavior. This approach promotes code reuse without inheritance hierarchies.

module Loggable
  def execute
    log_before
    result = super
    log_after
    result
  end
  
  private
  
  def log_before
    puts "Executing #{self.class.name}"
  end
  
  def log_after
    puts "Completed #{self.class.name}"
  end
end

class SaveCommand
  include Loggable
  
  def initialize(document)
    @document = document
  end
  
  def execute
    @document.save
  end
end

Method objects provide another implementation approach. Ruby's method method returns a Method object representing a bound method call. These objects support the call interface, making them compatible with command semantics. Method objects capture the receiver implicitly, reducing command class proliferation.

class Calculator
  def add(x, y)
    x + y
  end
  
  def subtract(x, y)
    x - y
  end
end

calc = Calculator.new
add_command = calc.method(:add)
result = add_command.call(5, 3)
# => 8

Command queues leverage Ruby's Array and Queue classes. Commands accumulate in collections and execute in order. Thread-safe queues support concurrent command execution. Priority queues implement command scheduling based on precedence.

class CommandQueue
  def initialize
    @commands = []
  end
  
  def add(command)
    @commands << command
  end
  
  def execute_all
    @commands.each(&:execute)
    @commands.clear
  end
end

queue = CommandQueue.new
queue.add(LightOnCommand.new(bedroom_light))
queue.add(LightOnCommand.new(kitchen_light))
queue.execute_all

Thread-based command execution requires thread-safe implementations. Commands executing across threads must handle shared state carefully. Ruby's Mutex class provides synchronization. Thread-local storage isolates per-thread state.

require 'thread'

class AsyncCommandExecutor
  def initialize
    @queue = Queue.new
    @thread = Thread.new { run_loop }
  end
  
  def submit(command)
    @queue << command
  end
  
  private
  
  def run_loop
    loop do
      command = @queue.pop
      command.execute
    end
  end
end

Design Considerations

The Command Pattern suits scenarios requiring operation parameterization. When objects need to be configured with operations at runtime, commands provide the necessary indirection. Callbacks, event handlers, and menu actions benefit from command-based designs. The pattern excels when operations must be specified independently of their receivers.

Command Pattern adds complexity through additional classes and indirection. Simple applications with direct method calls may not justify this overhead. The pattern's benefits emerge when dealing with undo/redo, operation queuing, transaction management, or macro recording. Projects without these requirements should consider simpler alternatives.

The pattern competes with several alternatives. Strategy Pattern also encapsulates algorithms as objects but focuses on algorithmic variation rather than request encapsulation. Callback functions provide lightweight operation parameterization without command infrastructure. Event systems offer publish-subscribe semantics for decoupled communication. The choice depends on whether the problem involves encapsulating requests or selecting algorithms.

Commands introduce memory overhead through object allocation. Each command instance occupies memory until execution completes. Long-lived command queues or extensive undo histories accumulate objects. Memory-constrained environments may require command pooling or history limits. The overhead typically remains negligible compared to the flexibility gained.

Undo support significantly increases implementation complexity. Commands must capture or store state needed for reversal. Some operations resist undo implementation, particularly those with external side effects like network requests or file deletions. Compensating transactions may be necessary for irreversible operations. The undo interface should clearly document which commands support reversal.

Thread safety requires careful consideration in concurrent environments. Commands executing on multiple threads must handle shared state appropriately. Immutable commands avoid synchronization overhead. Mutable commands need explicit locking. Command queues require thread-safe implementations. The threading model should be decided early as retrofitting thread safety proves difficult.

Command lifetimes vary based on usage patterns. Transient commands execute once and become eligible for garbage collection. Persistent commands remain in undo stacks or queues for extended periods. Serializable commands may be saved to disk for durability. Lifetime requirements influence state management and resource cleanup strategies.

The pattern's granularity affects design flexibility. Fine-grained commands represent atomic operations, enabling flexible composition but increasing class count. Coarse-grained commands bundle multiple operations, reducing class proliferation but limiting reuse. The appropriate granularity depends on composition requirements and operation atomicity.

# Fine-grained commands
class SetWidthCommand
  def initialize(shape, width)
    @shape = shape
    @width = width
    @old_width = nil
  end
  
  def execute
    @old_width = @shape.width
    @shape.width = @width
  end
  
  def unexecute
    @shape.width = @old_width
  end
end

# Coarse-grained command
class ResizeShapeCommand
  def initialize(shape, width, height)
    @shape = shape
    @width = width
    @height = height
    @old_dimensions = nil
  end
  
  def execute
    @old_dimensions = { width: @shape.width, height: @shape.height }
    @shape.width = @width
    @shape.height = @height
  end
  
  def unexecute
    @shape.width = @old_dimensions[:width]
    @shape.height = @old_dimensions[:height]
  end
end

Common Patterns

Macro commands aggregate multiple commands into composite operations. The macro command stores a collection of subcommands and executes them sequentially. This pattern implements transaction-like behavior where multiple operations execute as a unit. Macro undo reverses subcommands in reverse order to maintain consistency.

class MacroCommand
  def initialize
    @commands = []
  end
  
  def add(command)
    @commands << command
  end
  
  def execute
    @commands.each(&:execute)
  end
  
  def unexecute
    @commands.reverse_each(&:unexecute)
  end
end

# Creating a macro
open_file = OpenFileCommand.new(editor, "document.txt")
find_text = FindCommand.new(editor, "TODO")
replace_text = ReplaceCommand.new(editor, "DONE")

macro = MacroCommand.new
macro.add(open_file)
macro.add(find_text)
macro.add(replace_text)
macro.execute

Command history implements undo/redo functionality through stacks. The history maintains executed commands in a stack. Undo pops commands from the history, calls unexecute, and pushes them to a redo stack. Redo reverses this process. History limits prevent unbounded memory growth.

class CommandHistory
  def initialize(limit = 100)
    @history = []
    @undone = []
    @limit = limit
  end
  
  def execute(command)
    command.execute
    @history.push(command)
    @history.shift if @history.length > @limit
    @undone.clear
  end
  
  def undo
    return if @history.empty?
    
    command = @history.pop
    command.unexecute
    @undone.push(command)
  end
  
  def redo
    return if @undone.empty?
    
    command = @undone.pop
    command.execute
    @history.push(command)
  end
  
  def can_undo?
    !@history.empty?
  end
  
  def can_redo?
    !@undone.empty?
  end
end

Null commands implement the Null Object pattern within the command context. Null commands provide do-nothing implementations of the command interface. They eliminate nil checks in client code and provide default behaviors. This pattern proves useful for optional commands or unassigned slots.

class NullCommand
  def execute
    # Do nothing
  end
  
  def unexecute
    # Do nothing
  end
end

class RemoteControl
  def initialize
    @commands = Hash.new(NullCommand.new)
  end
  
  def set_command(slot, command)
    @commands[slot] = command
  end
  
  def press_button(slot)
    @commands[slot].execute
  end
end

Command processors add cross-cutting concerns to command execution. Processors wrap commands and inject behavior like logging, timing, or validation. The decorator pattern enables stacking multiple processors. This approach centralizes concerns rather than duplicating code across command classes.

class LoggingProcessor
  def initialize(command)
    @command = command
  end
  
  def execute
    puts "Starting: #{@command.class.name}"
    start_time = Time.now
    result = @command.execute
    elapsed = Time.now - start_time
    puts "Completed in #{elapsed}s"
    result
  end
end

class ValidatingProcessor
  def initialize(command)
    @command = command
  end
  
  def execute
    raise "Invalid command state" unless @command.valid?
    @command.execute
  end
end

Transactional commands implement all-or-nothing semantics. The command executes multiple operations within a transaction boundary. If any operation fails, the entire transaction rolls back. This pattern requires commands to support partial undo and rollback checkpoints.

class TransactionalCommand
  def initialize(commands)
    @commands = commands
    @executed = []
  end
  
  def execute
    @commands.each do |cmd|
      begin
        cmd.execute
        @executed << cmd
      rescue StandardError => e
        rollback
        raise e
      end
    end
  end
  
  private
  
  def rollback
    @executed.reverse_each do |cmd|
      cmd.unexecute rescue nil
    end
    @executed.clear
  end
end

Scheduled commands defer execution until specified conditions are met. Time-based scheduling executes commands at future times. Condition-based scheduling waits for specific states. This pattern requires a scheduler that monitors conditions and triggers execution.

class ScheduledCommand
  def initialize(command, execute_at)
    @command = command
    @execute_at = execute_at
  end
  
  def ready?
    Time.now >= @execute_at
  end
  
  def execute
    @command.execute if ready?
  end
end

class CommandScheduler
  def initialize
    @scheduled = []
  end
  
  def schedule(command, delay)
    execute_at = Time.now + delay
    @scheduled << ScheduledCommand.new(command, execute_at)
  end
  
  def process
    @scheduled.select(&:ready?).each do |cmd|
      cmd.execute
      @scheduled.delete(cmd)
    end
  end
end

Practical Examples

Text editor undo/redo demonstrates classic Command Pattern application. Each editing operation becomes a command supporting both execution and reversal. The editor maintains command history enabling multi-level undo. Commands capture document state needed for accurate undo.

class Document
  attr_accessor :content, :cursor
  
  def initialize
    @content = ""
    @cursor = 0
  end
  
  def insert(text)
    @content.insert(@cursor, text)
    @cursor += text.length
  end
  
  def delete(position, length)
    @content.slice!(position, length)
    @cursor = position
  end
end

class InsertCommand
  def initialize(document, text)
    @document = document
    @text = text
    @position = nil
  end
  
  def execute
    @position = @document.cursor
    @document.insert(@text)
  end
  
  def unexecute
    @document.delete(@position, @text.length)
  end
end

class DeleteCommand
  def initialize(document, length)
    @document = document
    @length = length
    @position = nil
    @deleted_text = nil
  end
  
  def execute
    @position = @document.cursor
    @deleted_text = @document.content[@position, @length]
    @document.delete(@position, @length)
  end
  
  def unexecute
    @document.cursor = @position
    @document.insert(@deleted_text)
  end
end

# Usage
doc = Document.new
history = CommandHistory.new

# Type "Hello"
insert = InsertCommand.new(doc, "Hello")
history.execute(insert)

# Type " World"
insert2 = InsertCommand.new(doc, " World")
history.execute(insert2)

puts doc.content
# => Hello World

history.undo
puts doc.content
# => Hello

history.redo
puts doc.content
# => Hello World

Database transaction management leverages commands for atomicity. Each database operation becomes a command. A transaction aggregates commands and executes them within a single database transaction. Failure triggers rollback of all operations.

class DatabaseCommand
  def initialize(connection)
    @connection = connection
  end
  
  def execute
    raise NotImplementedError
  end
  
  def compensate
    raise NotImplementedError
  end
end

class InsertRecordCommand < DatabaseCommand
  def initialize(connection, table, record)
    super(connection)
    @table = table
    @record = record
    @inserted_id = nil
  end
  
  def execute
    @inserted_id = @connection.insert(@table, @record)
  end
  
  def compensate
    @connection.delete(@table, @inserted_id) if @inserted_id
  end
end

class UpdateRecordCommand < DatabaseCommand
  def initialize(connection, table, id, updates)
    super(connection)
    @table = table
    @id = id
    @updates = updates
    @old_values = nil
  end
  
  def execute
    @old_values = @connection.get(@table, @id)
    @connection.update(@table, @id, @updates)
  end
  
  def compensate
    @connection.update(@table, @id, @old_values) if @old_values
  end
end

class DatabaseTransaction
  def initialize(connection)
    @connection = connection
    @commands = []
    @executed = []
  end
  
  def add(command)
    @commands << command
  end
  
  def commit
    @connection.begin_transaction
    
    @commands.each do |cmd|
      cmd.execute
      @executed << cmd
    end
    
    @connection.commit
  rescue StandardError => e
    rollback
    raise e
  end
  
  private
  
  def rollback
    @connection.rollback
    @executed.reverse_each { |cmd| cmd.compensate }
  end
end

Home automation systems use commands to control devices. Each device action becomes a command. Remote controls trigger commands without knowing device details. Macro commands implement scenes combining multiple device operations.

class SmartHome
  class Light
    attr_reader :level
    
    def initialize(name)
      @name = name
      @level = 0
    end
    
    def set_level(level)
      @level = level.clamp(0, 100)
      puts "#{@name} set to #{@level}%"
    end
  end
  
  class Thermostat
    attr_reader :temperature
    
    def initialize
      @temperature = 72
    end
    
    def set_temperature(temp)
      @temperature = temp
      puts "Temperature set to #{temp}°F"
    end
  end
end

class LightCommand
  def initialize(light, level)
    @light = light
    @level = level
    @previous_level = nil
  end
  
  def execute
    @previous_level = @light.level
    @light.set_level(@level)
  end
  
  def unexecute
    @light.set_level(@previous_level)
  end
end

class ThermostatCommand
  def initialize(thermostat, temperature)
    @thermostat = thermostat
    @temperature = temperature
    @previous_temp = nil
  end
  
  def execute
    @previous_temp = @thermostat.temperature
    @thermostat.set_temperature(@temperature)
  end
  
  def unexecute
    @thermostat.set_temperature(@previous_temp)
  end
end

# Create scene
living_room_light = SmartHome::Light.new("Living Room")
bedroom_light = SmartHome::Light.new("Bedroom")
thermostat = SmartHome::Thermostat.new

evening_scene = MacroCommand.new
evening_scene.add(LightCommand.new(living_room_light, 75))
evening_scene.add(LightCommand.new(bedroom_light, 30))
evening_scene.add(ThermostatCommand.new(thermostat, 68))

evening_scene.execute
# => Living Room set to 75%
# => Bedroom set to 30%
# => Temperature set to 68°F

Job scheduling systems queue commands for asynchronous execution. Commands represent background tasks. The scheduler processes queued commands based on priority and resource availability. Failed commands can be retried or moved to error queues.

class BackgroundJob
  attr_reader :priority, :created_at
  
  def initialize(priority: 5)
    @priority = priority
    @created_at = Time.now
  end
  
  def execute
    raise NotImplementedError
  end
end

class EmailJob < BackgroundJob
  def initialize(recipient, subject, body)
    super()
    @recipient = recipient
    @subject = subject
    @body = body
  end
  
  def execute
    # Send email
    puts "Sending email to #{@recipient}: #{@subject}"
  end
end

class DataProcessingJob < BackgroundJob
  def initialize(dataset, priority: 5)
    super(priority: priority)
    @dataset = dataset
  end
  
  def execute
    puts "Processing dataset: #{@dataset}"
    # Perform data processing
  end
end

class JobQueue
  def initialize
    @queue = []
    @failed = []
  end
  
  def enqueue(job)
    @queue << job
    @queue.sort_by! { |j| [-j.priority, j.created_at] }
  end
  
  def process_next
    return if @queue.empty?
    
    job = @queue.shift
    job.execute
  rescue StandardError => e
    @failed << { job: job, error: e }
    puts "Job failed: #{e.message}"
  end
  
  def process_all
    process_next until @queue.empty?
  end
  
  def failed_jobs
    @failed
  end
end

# Usage
queue = JobQueue.new
queue.enqueue(EmailJob.new("user@example.com", "Welcome", "Hello!"))
queue.enqueue(DataProcessingJob.new("sales_data.csv", priority: 10))
queue.process_all

Reference

Command Pattern Components

Component Responsibility Implementation
Command Declares execution interface Abstract class or module defining execute method
ConcreteCommand Implements operation and binds receiver Class implementing execute with receiver delegation
Receiver Performs actual work Domain object with business logic methods
Invoker Triggers command execution Stores and invokes commands without knowing details
Client Creates and configures commands Instantiates commands and sets receivers

Command Interface Methods

Method Purpose Required
execute Performs the command operation Yes
unexecute Reverses the command operation No
valid? Validates command state No
compensate Provides rollback for irreversible operations No

Command Pattern Variations

Pattern Description Use Case
Simple Command Basic execute method only One-way operations without undo
Undoable Command Adds unexecute method Text editors, drawing applications
Macro Command Aggregates multiple commands Composite operations, scenes
Transactional Command All-or-nothing execution Database operations, distributed systems
Queued Command Deferred execution Background jobs, task scheduling
Logged Command Records execution history Audit trails, debugging
Remote Command Serializable for network transmission Distributed systems, RPC

Implementation Approaches

Approach Advantages Disadvantages
Class-based Full OOP features, clear structure Increased class count
Proc-based Concise for simple commands Limited undo support
Method objects Leverages Ruby method system Less explicit command structure
Module mixins Shared behavior reuse Potential mixin conflicts

Common Command Operations

Operation Implementation Complexity
Execute Call receiver method with parameters O(1)
Undo Restore previous state or apply inverse O(1) to O(n)
Redo Re-execute previously undone command O(1)
Serialize Convert command to portable format O(n) where n is state size
Clone Create independent command copy O(n) where n is state size

Design Decision Matrix

Requirement Recommended Approach Justification
Simple callbacks Proc or lambda Minimal overhead, sufficient functionality
Undo support Class-based command Need state storage and unexecute logic
Command queuing Class-based with history Explicit control over execution timing
Transaction support Transactional command pattern Requires rollback and compensation
Cross-cutting concerns Command processor wrappers Centralizes logging, validation, etc.
Macro operations Macro command composite Groups related operations
Async execution Thread-safe queue Handles concurrent execution

State Management Strategies

Strategy Description Memory Impact
Memento Store complete prior state High for large objects
Delta Store only changed values Low to medium
Reverse operation Calculate inverse operation Minimal
Copy-on-write Share immutable state Low with structural sharing

Undo Implementation Patterns

Pattern Mechanism Limitations
State restoration Save and restore receiver state Memory intensive
Inverse operation Apply opposite operation Not all operations have inverses
Compensation Execute compensating transaction Approximate reversal only
Snapshot chain Link commands with state snapshots Complex lifecycle management