This page looks best with JavaScript enabled

Avoid long If/else with a Strategy Pattern

 ·  ☕ 5 min read  ·  ✍️ Iskander Samatov

Strategy Pattern



In this post, let’s cover Strategy Pattern. This pattern will help you write loosely coupled code and avoid long if/else statements.

Strategy Pattern over if/else

One particular piece of syntax that is unlikely to ever change is if/else and switch statements. I don’t know about you, but I always found them to be the ugliest part of my code. However, it’s a terribly necessary evil that is impossible to avoid.

In business applications we almost always face a situation where we have to implement some kind of routing method that performs a variation of the certain action based on the condition. These methods usually end up having long and ugly if/else or switch statements. (Payment options, email routing, multiple authentication providers, and etc.)

In this post I want to share with you a pattern that I learned about recently myself and which I think can help you avoid some of those scenarios.

What is Strategy Pattern?

The pattern that I am talking about is called Strategy. This pattern was popularized by GoF ( Gang of Four ) and is a great tool for adding mutable, interchangeable parts to certain pieces of your application logic.

So how does it work? Strategy implements the variable parts of the functionality with an agreed upon interface for otherwise generic object which we call “Context”. The context uses different strategies to perform a certain task based on the client’s invocation.

Think of the set of strategies as a toolbox and the context as a craftsman. The craftsman uses a different tool depending on the job. But in this case, all the tools have the same type of instructions on how to use them.

Here’s a diagram to further elaborate on this idea:

strategy pattern
strategy pattern diagram

Example of Strategy Pattern

Passport.JS is a perfect example of the usage of the Strategy pattern. Passport.JS is a library for easily integrating authentication system in your Node.JS application. It has multiple different interfaces for setting up the authentication based on the provider you want to use (Facebook, Google, Twitter and etc). All you need to do is plug in the interface you need into your authentication logic. Plus, all of those interfaces are interchangeable and you can easily swap one for another.

Receipt sender

I’m sure by now you have at least a basic understanding of the Strategy pattern so enough of the theory! Let’s try to build a simple system for delivering receipts to the users based on the client they used to place the order.

Define strategies

Let’s start off by fleshing out our strategies. First let define our email strategy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const mailgun = require('mailgun-js')({
    apiKey: api_key,
    domain: domain
});

const EmailStrategy = {
    send: function(receiptHtml, userEmail) {
        const emailBody = getEmailBody(receiptHtml, userEmail);
        mailgun.messages().send(emailBody, function (error, body) {
            console.log(body);
        });
    }
}

We’re using the mailgun module to send the emails. The strategy object has one method send which accepts receiptHtml and userEmail for parameters.

Now let’s move on to our Slack strategy:

1
2
3
4
5
6
7
8
9
const slack = require('slack')

const SlackStrategy = {
    send: function(receiptHtml, userEmail) {
        const user = getUserFromEmail(userEmail);
        const slackMessageBody = getSlackMessageBody(receiptHtml, userEmail);
        slack.chat.postEphemeral({ token, channel, slackMessageBody, user })
    }
}

We’re using slack’s npm library to send the receipt through our imaginary slack bot. One important thing to note here:

  • Strategy’s send method signature is identical to its email equivalent

Receipt manager

Now that we have defined our strategies, let’s define our receipt manager class which will generate the html for the receipt and use our strategies to send the receipt to the user:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class ReceiptManager {

    _generateReceiptHtml(orderId, userEmail) {
        // generates and returns HTML of the receipt
    }
    

    sendReceipt(orderId, userEmail, sendStrategy) {
        const receiptHTML = this._generateReceiptHtml(orderId, userEmail);
        sendStrategy.send(receiptHTML, userEmail);
    }
}

I omitted the implementation of the _generateReceiptHtml method as it’s not relevant to our example. The interesting part is the sendReceipt method:

  • We generate the html for the receipt using _generateReceiptHtml
  • We send the receipt using the strategy that was passed to us as an argument

Since both of our strategies have the same method signature we are able to use them interchangeably without the ReceiptManager being aware of the difference.

Now let’s look at how the sendReceipt would look if we didn’t define our strategies:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sendReceipt(orderId, userEmail, client) {
        const receiptHTML = this._generateReceiptHtml(orderId, userEmail);
        switch(client) {
            case 'email': {
                sendEmail(receiptHTML, userEmail);
                break;
            }
            case 'slack': {
                sendSlackMessage(receiptHTML, userEmail);
                break;
            }
        }
}

Notice how the switch statement decreased the readability of our code. This method might not look too bad right now, but later on, if we decide to add more channels for delivering the receipts, this method will quickly become ugly.

Client

Now for the final piece, let’s look at how easy it is to use our strategies together with our receipt manager:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const ReceiptManager = require('./receipt-manager');
const {
    EmailStrategy,
    SlackStrategy,
} = require('./strategy');

function main(orderId, userEmail) {
    const manager = new ReceiptManager();

    const email = new EmailStrategy();
    manager.sendReceipt(orderId, userEmail, email);

    const slack = new SlackStrategy();
    manager.sendReceipt(orderId, userEmail, slack);
}

Simple isn’t it? All we need do is instantiate the appropriate strategy depending on the channel we want to use and pass it on to our receipt manager.

And that’s it for this post! Small disclaimer: Strategy pattern might not be applicable to each and every case and sometimes might be an overkill. Because at the end of the day, this pattern is just a tool, and just like any tool you need to make sure it fits the job.

Nevertheless, Strategy can be a great way to enforce a separation of concerns and make the parts of your code much more modular and interchangeable.

Thank you for reading!

Share on

Software Development Tutorials
WRITTEN BY
Iskander Samatov
The best up-to-date tutorials on web and mobile development.