Like a grocery list, a Command pattern command is an instruction to do something, something specific. With the Command pattern, we construct objects that know how to perform some very specific actions. Commands are useful for keeping a running list of things that your program needs to do, or for remembering what it has already done. You can also run your commands backwards and undo the things that your program has done.

An Explosion of Subclasses

To apply the Command pattern to a button that listening to click event, we simply store a command object with each button.

class SlickButton
  attr_accessor :command
  def initialize(command)
    @command = command
	end
  #
  # Lots of button drawing and management
  # code omitted...
  #
  def on_button_push
    @command.execute if @command
	end 
end

We can then define different commands for all of the things that our buttons

class SaveCommand
  def execute
    #
    # Save the current document...
    #
	end 
end

# Call the Save button
save_button = SlickButton.new( SaveCommand.new )

Commands That Record

We are trying to keep track of what we are about to do—or have done—we will need a class to collect all of our commands. Hmm, a class that acts like a command, but really is just a front for a number of subcommands. Sounds like a composite:

class CompositeCommand < Command
  def initialize
    @commands = []
  end

  def add_command(cmd)
    @commands << cmd
	end

  def execute
    @commands.each {|cmd| cmd.execute}
	end

  def description
    description = ''
    @commands.each {|cmd| description += cmd.description + "\\n"}
    description
	end 
end

Other than being a nice use of another pattern, CompositeCommand allows us to tell the user exactly what we are doing to his or her system. We could, for example, create a new file, copy it to a second file, and then delete the first file:

cmds = CompositeCommand.new
cmds.add_command(CreateFile.new('file1.txt', "hello world\\n"))
cmds.add_command(CopyFile.new('file1.txt', 'file2.txt'))
cmds.add_command(DeleteFile.new('file1.txt'))

# To actually execute all of these file comings and goings, we simply call execute: 
cmds.execute

Being Undone by a Command

When you use this pattern, you are no longer simply saying, “Do this”; instead, you are saying, “Remember how to do this,” and, sometime later, “Do that thing that I told you to remember.”

Redo—that is, the ability to unchange your mind and to reapply the change that you just undid—falls elegantly out of the same design. To redo something, we just start re-executing the commands, starting with the last one that was undone. We start by adding an unexecute method to the CreateFile command:

class CreateFile < Command
  def initialize(path, contents)
    super "Create file: #{path}"
		@path = path
    @contents = contents
  end

  def execute
    f = File.open(@path, "w")
    f.write(@contents)
    f.close
	end
  
	def unexecute
    File.delete(@path)
	end 
end

Things get a little more challenging with the DeleteCommand, because this command is inherently destructive: To undo a delete operation, we need to save the contents of the original file, *before we delete it* In a real system, we would probably copy the contents of the file to some temporary directory, but for our example we will just read them into memory:

class DeleteFile < Command
  def initialize(path)
    super "Delete file: #{path}"
    @path = path
  end

	def execute
    if File.exists?(@path)
      @contents = File.read(@path)
    end
    f = File.delete(@path)
  end

  def unexecute
    if @contents
      f = File.open(@path,"w")
      f.write(@contents)
      f.close
		end 
	end
end