The Command Design Pattern in JavaScript

jsmanifest

jsmanifest

Posted on December 24, 2019

The Command Design Pattern in JavaScript

Find me on medium

In JavaScript, one of the most popular design patterns that people like to use is the Command Design Pattern, a pattern that allows developers to separate objects that request something from those that want to call their desired methods.

If this is your first time hearing about the command pattern, hopefully by reading this post you will gain a good understanding of what it is, how it works and why we need them in certain situations.

What is the command design pattern?

Design patterns are usually categorized between three different types of categories, and in this case the command pattern falls into the behavioral one.

The reason why is because its purpose is to encapsulate objects that have the double responsibility of deciding which methods to call and what happens inside.

In a visual perspective, that may look something like:

command pattern visual 1

How it works

So essentially, its duty is to split the communication to separate objects so that they become loosely coupled while still maintaining the end goal.

The participants that are involved in this pattern are commonly referred to as:

Client

The client's responsibility is to create the command object and pass it to the invoker.

Invoker

The invoker receives the command object from the client and its only responsibility is to call (or invoke) a command.

Receiver

Then, the receiver receives the command and looks for a method to call based on the received command.

How it looks like

We've just seen an image of how one or more objects behave in code before being applied with the command pattern. Here is how it would look like with it applied:

One evidently large and complex object can end up becoming easier to manage in the long run because the duties of one object vs another were isolated in their own private world instead of being cluttered together.

Command objects by convention usually define a method with a name like execute which has the responsibility of invoking a method, which by convention is known as the invoker. The object that holds the methods is commonly known as the "receiver".

Why we need the command pattern

The biggest point of using the command pattern is to split the code that wants to do something from the code that is responsible for handling it. When you feel like your code is handling an operation multiple times in different parts of the code, it may become a good idea to start applying it. Having said that, these command objects give us nice benefits for unique situations like being able to centralize the processing of each action/operation individually. This means that in our earlier example our object only needs one .eat() command, one .jump() command, and one .run() command.

When to use

Some example situations in which you can make great use of the command pattern are:

  • Undo / Reset
    • Since all processing of each action/operations are centralized by commands, they are often fit for implementing undo/reset for applications.
  • You need a command to have a life span independent of the original request.
  • Furthermore, if you want to queue, specify and execute requests at different times.
  • You need undo/redo operations. The command's execution can be stored for reversing its effects. It is important that the Command class implements the methods undo and redo.
  • You need to structure a system around high-level operations built on primitive operations.

Real world example

Now lets pretend we are launching a new frog manager application which is intended to help you record and manage a list of frogs over time as they age.

In this application, we're going to have a Frog class, instantiating some useful properties and methods to help with that:

// Creates and returns a frog api which can help us track activities of each frog
function createFrog(options) {
  const _opts = {
    name: options.name,
    sex: options.sex,
    age: options.age,
  }

  const foodsEaten = []
  const wordsSpoken = []

  return {
    getOption(key) {
      return _opts[key]
    },
    getFoodsConsumed() {
      return foodsEaten
    },
    getWordsSpoken() {
      return wordsSpoken
    },
    eat(food) {
      console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
      foodsEaten.push(food)
    },
    talk(words) {
      console.log(words)
      wordsSpoken.push(...words)
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

Great! Now we can make multiple frogs by instantiating them:

const mikeTheFrog = createFrog({ name: 'mike', sex: 'male', age: 1 })
const sallyTheOtherFrog = createFrog({ name: 'sally', sex: 'female', age: 4 })
const michelleTheLastFrog = createFrog({
  name: 'michelle',
  sex: 'female',
  age: 10,
})
Enter fullscreen mode Exit fullscreen mode

Lets pretend move on to making our frog application come to life:

index.js

const api = {
  fetchFrogs: function() {
    return Promise.resolve([
      { id: 1, name: 'mike', sex: 'male', age: 1 },
      { id: 2, name: 'sally', sex: 'female', age: 2 },
      { id: 3, name: 'michelle', sex: 'female', age: 9 },
    ])
  },
  saveToDb: function(frogs) {
    // Just pretend this is actually saving to a real database
    console.log(`Saving ${frogs.length} frogs to our database...`)
    return Promise.resolve()
  },
}

async function init() {
  try {
    const frogs = await api.fetchFrogs()
    return frogs.map((data) => createFrog(data))
  } catch (error) {
    console.error(error)
    throw error
  }
}

function createFrogsManager() {
  const frogs = []

  return {
    addFrog(frog) {
      frogs.push(frog)
      return this
    },
    getFrogs() {
      return frogs
    },
    getMaleFrogs() {
      return frogs.filter((frog) => {
        return frog.getOption('sex') === 'male'
      })
    },
    getFemaleFrogs() {
      return frogs.filter((frog) => {
        return frog.getOption('sex') === 'female'
      })
    },
    feedFrogs(food) {
      frogs.forEach((frog) => {
        frog.eat(food)
      })
      return this
    },
    save: function() {
      return Promise.resolve(api.saveToDb(frogs))
    },
  }
}

function Food(name, type, calories) {
  this.name = name
  this.type = type
  this.calories = calories
}

const fly = new Food('fly', 'insect', 1.5)
const dragonfly = new Food('dragonfly', 'insect', 4)
const mosquito = new Food('mosquito', 'insect', 1.8)
const apple = new Food('apple', 'fruit', 95)

init()
  .then((frogs) => {
    const frogsManager = createFrogsManager()
    // Add each fetched frog to our managing list so we can manage them
    frogs.forEach((frog) => {
      frogsManager.addFrog(frog)
    })

    const genders = {
      males: frogsManager.getMaleFrogs(),
      females: frogsManager.getFemaleFrogs(),
    }
    // Lets feed the frogs and then save this new data to the database
    frogsManager
      .feedFrogs(fly)
      .feedFrogs(mosquito)
      .save()
    console.log(
      'We reached the end and our database is now updated with new data!',
    )
    console.log(
      `Fed: ${genders.males.length} male frogs and ${genders.females.length} female frogs`,
    )
    frogsManager.getFrogs().forEach((frog) => {
      console.log(
        `Frog ${frog.getOption('name')} consumed: ${frog
          .getFoodsConsumed()
          .map((food) => food.name)
          .join(', ')}`,
      )
    })
  })
  .catch((error) => {
    console.error(error)
  })
Enter fullscreen mode Exit fullscreen mode

Result:

command design pattern test prod frog code

Our application is becoming extremely valuable!

Now keep in mind that we did not apply the command design pattern in the code--however the code runs perfectly fine and we can be fine if our frog application wasn't going to grow any bigger.

Now lets take a real close look to our createFrogsManager api. We can see that this gives us an api to manage a list of frogs over time by providing convenient utilities to track the activities of multiple frogs.

However, if you look closely there are some potential issues that can bite us in the future.

The first thing we see is that our api createFrogsManager is tightly coupled with carrying out the methods that we want to work with. Our code at the end utilizes this interface and directly invokes its methods, being entirely dependent on the returned api. This api is responsible for both invoking and handling each operation.

For example, lets talk about these two methods returned for us to use:

getMaleFrogs() {
  return frogs.filter((frog) => {
    return frog.getOption('sex') === 'male'
  })
},
getFemaleFrogs() {
  return frogs.filter((frog) => {
    return frog.getOption('sex') === 'female'
  })
}
Enter fullscreen mode Exit fullscreen mode

What if in the future the path to get each frog's gender was slightly changed?

So instead of this:

function createFrog(options) {
  const _opts = {
    name: options.name,
    sex: options.sex,
    age: options.age,
  }

  const foodsEaten = []
  const wordsSpoken = []

  return {
    getOption(key) {
      return _opts[key]
    },
    getFoodsConsumed() {
      return foodsEaten
    },
    getWordsSpoken() {
      return wordsSpoken
    },
    eat(food) {
      console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
      foodsEaten.push(food)
    },
    talk(words) {
      console.log(words)
      wordsSpoken.push(...words)
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

It became this instead:

function createFrog(options) {
  const _opts = {
    name: options.name,
    gender: options.gender,
    age: options.age,
  }

  const foodsEaten = []
  const wordsSpoken = []

  return {
    getOption(key) {
      return _opts[key]
    },
    getFoodsEaten() {
      return foodsEaten
    },
    getWordsSpoken() {
      return wordsSpoken
    },
    eat(food) {
      console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
      foodsEaten.push(food)
    },
    talk(words) {
      console.log(words)
      wordsSpoken.push(...words)
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

Days have passed and things have been silent. No reports of complaints so everything must be fine. After all, our server has been up and running 24/7 and users have been using our application since then.

Then, a customer had called our customer service department 2 weeks later and reported that all of her frogs died and blamed our platform for her loss after placing her entire trust into us believing that our intelligent algorithms would help her make the right decisions for keeping them properly managed.

Our developers were immediately notified and were asked to debug the situation to see if there were any glitches in code that might have sparked this horrifying event.

At closer examination, we run a test code and realized that our code is actually reporting incorrect information!

pre-applied command design pattern in javascript data mismatch

What?! No way!

One of the developers pointed out that the problem was that the .sex key of a frog object was renamed to .gender!

const _opts = {
  name: options.name,
  gender: options.gender,
  age: options.age,
}
Enter fullscreen mode Exit fullscreen mode

We had to go find and change the code that used the previous references by key so that it works normally again:

getMaleFrogs() {
  return frogs.filter((frog) => {
    return frog.getOption('gender') === 'male'
  })
},
getFemaleFrogs() {
  return frogs.filter((frog) => {
    return frog.getOption('gender') === 'female'
  })
    }
Enter fullscreen mode Exit fullscreen mode

Oh, and if you haven't caught it yet, there was another issue with our code. It seems the method getFoodsConsumed inside createFrog was also changed to getFoodsEaten:

Previous:

getFoodsConsumed() {
  return foodsEaten
}
Enter fullscreen mode Exit fullscreen mode

Current:

getFoodsEaten() {
  return foodsEaten
}
Enter fullscreen mode Exit fullscreen mode

In another scenario, what if the createFrogsManager api had some of its methods renamed, like .save to .saveFrogs or .getFrogs to .getAllFrogs? This means that every single part of our code that used these methods manually need to be updated to the new names!

So a major problem we're having here in the examples is that we're having to go fix all of our code that were affected to the change! It becomes a hide and seek game. But it doesn't need to be.

So how can the command pattern help turn this around?

In the beginning of this post we mentioned that the command pattern allows developers to separate objects that request something away from those that want to call their desired methods.

Also somewhere in the beginning of this post we mentioned the three participants that will be involved. They were the client, invoker and receiver.

Here is a representation of that:

command design pattern visual representation finale

Lets refactor our createFrogsManager using the command approach:

function createFrogsManager() {
  const frogs = []

  return {
    execute(command, ...args) {
      return command.execute(frogs, ...args)
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

This is all we really need because we're going to let the commands do the work.

We'll go ahead and create the Command constructor that we will use to create the concrete commands for each method of the api:

function Command(execute) {
  this.execute = execute
}
Enter fullscreen mode Exit fullscreen mode

Now that that's settled, lets go ahead and make the concrete commands:

function AddFrogCommand(frog) {
  return new Command(function(frogs) {
    frogs.push(frog)
  })
}

function GetFrogsCommand() {
  return new Command(function(frogs) {
    return frogs
  })
}

function FeedFrogsCommand(food) {
  return new Command(function(frogs) {
    frogs.forEach((frog) => {
      frog.eat(food)
    })
  })
}

function SaveCommand() {
  return new Command(function(frogs) {
    api.saveToDb(
      frogs.map((frog) => ({
        name: frog.name,
        gender: frog.gender,
        age: frog.age,
      })),
    )
  })
}
Enter fullscreen mode Exit fullscreen mode

With this in place, we can use it like so:

function Food(name, type, calories) {
  this.name = name
  this.type = type
  this.calories = calories
}

const mikeTheFrog = createFrog({
  name: 'mike',
  gender: 'male',
  age: 2,
})

const sallyTheFrog = createFrog({
  name: 'sally',
  gender: 'female',
  age: 1,
})

const frogsManager = createFrogsManager()
frogsManager.execute(new AddFrogCommand(mikeTheFrog))
frogsManager.execute(new FeedFrogsCommand(new Food('apple', 'fruit', 95)))
frogsManager.execute(new FeedFrogsCommand(new Food('fly', 'insect', 1)))
frogsManager.execute(new AddFrogCommand(sallyTheFrog))
frogsManager.execute(new SaveCommand())
const updatedFrogs = frogsManager.execute(new GetFrogsCommand())
Enter fullscreen mode Exit fullscreen mode

Result:

command design pattern saving frogs to database

I'd like to mention that in the visual, the receiver is blank because in JavaScript all functions and objects are basically commands themselves, which we demonstrated in the .execute by invoking commands directly:

function createFrogsManager() {
  const frogs = []

  return {
    execute(command, ...args) {
      return command.execute(frogs, ...args)
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

And that concludes the end of this post! I hope you found this to be valuable and look out for more in the future!

Find me on medium

💖 💪 🙅 🚩
jsmanifest
jsmanifest

Posted on December 24, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related