Design Patterns: The Command Pattern

command
Doug Yun

Engineering Manager

Doug Yun

Let’s get ready for some football!

One of my favorite sports is American football; it’s strategic, physical, and wild! As a fan - and once high school player - of the sport, I’ve gained some valuable lessons from my experiences. For example, I’ve learned that “persistence is key”, “giving up is for losers”, and that “water sucks, Gatorade is better.”

While those are fine gems of wisdom, today we’ll be covering one of the most overlooked teachings in football: the power of Command pattern.

The Command design pattern intends to separate and decouple an object of invocation from the object that receives the message of invocation. We will encapsulate all pertinent information of a method and execute the method at a later time. Essentially, the Command pattern gives us the ability to queue a series of operations for a later time. Let’s dig in.

Put me in, Coach!

Let’s start by creating a BostonNarwin class from which our football players will inherit from.

# football.rb

class BostonNarwin
  attr_reader :action

  def initialize(action)
    @action = action
  end

  def name
    self.class
  end
end

Next, we’ll need some key players; let’s create Quarterback and Receiver classes. For fun, we’re going to add a TeamOwner class too. All three of these classes are going to possess a method called #execute.

Each of these classes can be considered as instances of separate commands.

# football.rb

class Quarterback < BostonNarwin
  attr_reader :path, :play

  def initialize(path, play)
    super 'Hut! Hut! Red 19! Red 19! Hike!'
    @path = path
    @play = play
  end

  def execute
    file = File.open path, 'w'
    file.write "#{name}: #{play}\n"
    file.close
  end
end

class Receiver < BostonNarwin
  attr_reader :path, :play

  def initialize(path, play)
    super 'Run, run, run!!!'
    @path = path
    @play = play
  end

  def execute
    file = File.open path, 'a'
    file.write "#{name}: #{play}\n"
    file.close
  end
end

class TeamOwner < BostonNarwin
  attr_reader :path, :target

  def initialize(path, target)
    super "We are moving the team from #{prettify path} to #{prettify target}!"
    @path = path
    @target = target
  end

  def execute
    FileUtils.mv path, target
    file = File.open target, 'a'
    file.write "#{name}: We moved from #{prettify path} to #{prettify target}!"
    file.close
  end

  def prettify(pathname)
    (pathname.chomp File.extname(pathname)).capitalize
  end
end

Next, let’s create a class that keeps track of the Quarterback, Receiver, and TeamOwner commands. We can use the Composite pattern to create this new class.

# football.rb

class CompositeCommand < BostonNarwin
  attr_accessor :commands

  def initialize
    @commands = []
  end

  def add_command(*args)
    args.each { |arg| commands << arg }
  end

  def execute
    commands.each { |command| command.execute }
  end
end

Now, we can kickoff some football commands!

load 'football.rb'

quarterback = Quarterback.new('boston.txt', 'I'm going to throw a perfect pass!')
# => #<Quarterback:0x007ff6f5c5c148
     @action="Hut! Hut! Red 19! Red 19! Hike!",
     @path="boston.txt",
     @play="I'm going to throw a perfect pass!">

receiver = Receiver.new('boston.txt', 'I'm going to catch the ball!')
# => #<Receiver:0x007ff6f5c949f8
     @action="Run, run, run!!!",
     @path="boston.txt",
     @play="I'm going to catch the ball!">

team_owner = TeamOwner.new('boston.txt', 'somerville.txt')
# => #<TeamOwner:0x007ff6f5ccd028
     @action="We are moving the team from Boston to Somerville!",
     @path="boston.txt",
     @target="somerville.txt">

Great! Now we’ll create an instance of the CompositeCommand, add each sub-command with #add_command, and then execute each command with #execute.

command = CompositeCommand.new
# => #<CompositeCommand:0x007ff6f5b82948 @commands=[]>

command.add_command quarterback, receiver, team_owner
# => [#<Quarterback:0x007ff6f5c5c148
     @action="Hut! Hut! Red 19! Red 19! Hike!",
     @path="boston.txt",
     @play="I'm going to throw a perfect pass!">,
     #<Receiver:0x007ff6f5c949f8
     @action="Run, run, run!!!",
     @path="boston.txt",
     @play="I'm going to catch the ball!">,
     #<TeamOwner:0x007ff6f5ccd028
     @action="We are moving the team from Boston to Somerville!",
     @path="boston.txt",
     @target="somerville.txt">]

command.execute
# ...  Omitted for brevity ...

exit

Finally, let’s list out the files in our current directory and view the contents of our recently created text file.

$ ls
# => football.rb   somerville.txt

$ less somerville.txt
# => Quarterback: I'm going to throw a perfect pass!
     Receiver: I'm going to catch the ball!
     TeamOwner: We moved from Boston to Somerville!

Wow! The Command pattern in action!

Discussion

The Command pattern suggests that we create objects that perform specific tasks and actions. For our example, the Quarterback object created a file, the Receiver appended to the file, and the TeamOwner object moved it. Each of the command objects completed their action through CompositeCommand#execute.

Having one object, an instance of CompositeCommand, that executes all stored commands presents us with solutions ranging from simple file manipulation to user triggered interaction. The Command pattern also allows us to “store” and “remember” commands prior to and after execution.

Hope you enjoyed our example and go Boston Narwins!

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box