15 - [Design patterns series] - The Command Pattern

Alright folks, time to continue on our quest explaining design patterns in the most natural and easy to understand way. Normally I like to open up the blog with something personal, but nothing really interesting has happened in the last couple of days since my last post, so there’s that. Well, I saw the new Spider Man movie and it was insane. Anyway, let’s get down to learning a bit more about design patterns. Today we’ll go over the command pattern, which honestly, I’ve never used in production in my career, but now that I’ve revisited it I wonder why - there definitely have been a few cases it might have been super useful.

The Command Pattern

Ok, so to gain some understanding on why the command pattern is so cool and what it actually does, let’s carry on with our trip back in time to the early 2000’s. As you remember, we’re working at MySpace on the engineering team, and we develop all kinds of features for our users.

Today we found out that people update their usernames to look cool by adding a bunch of x’s or capitalizing their names, so something like XxX_Emil_xXx or eMiL. (sorry, i’m an engineer not a fiction writer, this is the best scenario I came up with, cut me some slack.)

We’re really cool at MySpace, so we want to provide some cool functionality to our users to be able to change their name with a click of a button. Let’s see how we might write that code:

class NameChanger {
    constructor(name) {
        this.name = name;
    }
    addExes() {
        this.name = `XxX_${this.name}_xXx`;
    }

    randomizeCaps() {
        const letters = this.name.split('');
        return letters.map((l, i) => {
            return i % 2 ? l.toUpperCase() : l.toLowerCase();
        }).join('');
    }
    
}

const nameChanger = new NameChanger('emil');
nameChanger.addExes(); // XxX_emil_XxX
nameChanger.randomizeCaps(); // xXx_eMiL_XxX

Ok, so this is pretty cool, we have given this beautiful functionality to our users which allows them to make their name look badass.

But so far, I kinda see a weakness here. What if the user isn’t a 12 year old boy that plays Call Of Duty for 8 hours a day and they want to change their username to its original version? How would we reverse that? Yeah sure, we can add another method that will look for x’s and underscores and trim them off, and then it would do the same for the capitalization, but all of that extra code, all of that risk… what if we were actually working on a serious application and we had to reverse a series of complex changes in database? What if our code was charging a customer bank account for a transaction and we realised it was the wrong transaction? Can you imagine the risk we’d have to take to reverse our changes and makinng sure that everything was restored back to its original state? Clearly we need to do better at MySpace.

Enter the Command Pattern.

The Command pattern’s idea is to take the different operations we want to do and encapsulate them in individual commands that have a perform and an undo method. This is the meat and potatoes, we want to do something, and then if we want to - we can undo it. Let’s see how we can refactor our NameChanger class to use the Command Pattern.

So first, we want to take both of our operations of our class, aka randomizing capitalization and adding x’s to their own individual commands.

class AddExesCommand {
	constructor(name) {
		this.name = name;
	}
	execute() {
		return `xXx_${this.name}_xXx`;
	}
	undo() {
		return this.name;
	}
}

class RandomizeCapsCommand {
	constructor(name) {
		this.name = name;
	}
	execute() {
		const letters = this.name.split('');
        return letters.map((l, i) => {
            return i % 2 ? l.toUpperCase() : l.toLowerCase();
        }).join('');
	}
	undo() {
		return this.name;
	}
}

As we can see, both of our components follow the same pattern:

  • We have a class with the name Command in it, so we can be reasonable and logical about our code naming
  • We have a constructor, to which we pass an initial value
  • We have an execute method that returns a modified version of the initially provided name value
  • We have an undo method that returns the initial value, untouched

And now let’s refactor our NameChanger class to actually use our commands:

class NameChanger {
    constructor(name) {
        this.name = name;
		this.history = [];
    }
	executeCommand(command) {
		this.name = command.execute(this.name);
		this.history.push(command);
	}
	undo() {
		const command = this.history.pop();
		this.name = command.undo();
	}
}

We have introduced a history property in our class, which is an array that will hold a reference to each command we execute. This will allow us to easily undo the last command we’ve called. Our name changer doesn’t have it’s own methods for updating the user’s name anymore. In stead, we have 2 methods, one being called executeCommand, that takes a command and calls its execute with the current value of this.name, and we have an undo method, which will pop the last command in the arraya and call it’s undo method, setting the value of this.name to the result.

Let’s see how we can use this in action:

const nameChanger = new NameChanger('emil')
namer.executeCommand(new AddExesCommand(nameChanger.name)); // xXx_emil_xXx
namer.executeCommand(new RandomizeCapsCommand(nameChanger.name)); // xXx_eMiL_XxX
namer.undo(); // xXx_emil_xXx
namer.undo(); // emil

Wow! This made everything so easy. We don’t need to worry about remembering what the initial name was, our commands do it for us!

Conclusion

The Command Pattern is incredibly useful, and while sometimes it might seem like a lot of overhead to separate operations into different commands - think about reusability - if we extract our operations into their own Command components - we can reuse the same command in multiple places in our system, making our code much more composable and separated. And think about how much control we get in situations where we want to invert a change. Like database changes or any important data we want to keep control of.

Until next time!


Written by Emil Mladenov - a slavic software developer who decided to use a blog as a digital rubber duck

I also have a podcast