The Strategy Pattern Using Higher Order Functions

3,659 reads

If your team is used to functional programming, then know that design patterns like the Strategy pattern can be used in a functional way as well.

The Strategy Pattern is usually implemented using classes. However, it can easily be achieved in a functional pattern.

Normally all strategy objects with the same interface can easily be swapped as needed. This is one of the main benefits of the stategy pattern.

For example, if you wanted to send an email, but wanted to implement a way to change between sending the email via Sparkpost or through AWS, you would do something like the following:

class EmailStrategy {

constructor() {}

execute(sender, receiver, subject, body) {

throw new Error('Not implemented');

}

}

class SparkPostEmailStrategy extends EmailStrategy {

constructor(apiKey, SparkPost) {

this.sparkpost = new SparkPost(apiKey);

}

execute(sender, receiver, subject, body) {

return this.sparkpost.transmissions.send({

content: {

from: sender,

subject,

html: body,

},

recipients: [

{address: sender}

]

});

}

}

class AWSEmailCommand extends IEmailCommand {

constructor(AWS) {

this.ses = new AWS.SES();

}

execute(sender, receiver, subject, body) {

return new Promise((resolve, reject) => {

this.ses.sendEmail({

Destination: {

ToAddresses: [receiver]

},

Message: {

Body: {

Html: {

Data: body,

},

},

Subject: {

Data: subject,

}

},

Source: sender,

}, (err, data) => {

if (err) reject(err);

resolve(data);

});

});

}

}

Now we can use the SparkPostEmailCommand and the AWSEmailCommand interchangeably:

const command = new SparkPostEmailCommand(myApiKey, SparkPost);

command.execute('us@email.com', 'them@email.com', title, body)

.then((data) => console.log('success'))

.catch((err) => console.error('failure', err));

But most Command objects are just two methods: a constructor and an execute method (and sometimes an undo method). It almost feels like when we call the constructor, we are just delaying the execution of the execute method until we want it do perform its side-effect.

Using Higher Order Functions

To me, this would be a great time to use higher order functions to simplify the code above and remove the need for classes in favor of simple functions.

Since most of the time, all we care about is setting up some dependencies, and then delaying execution, we could just write our Command classes as functions:

const sparkPostEmailCommand = (apiKey, SparkPost) => {

const sparkpost = new SparkPost(apiKey);

return (sender, receiver, subject, body) => {

return sparkpost.transmissions.send({

// Same as above

});

};

};

const awsEmailCommand = (AWS) => {

const ses = new AWS.SES();

return (sender, receiver, subject, body) => {

return new Promise((resolve, reject) => {

// Same as above

});

};

};

And now, instead of creating new objects, we can just use the command as functions:

sparkPostEmailCommand(myApiKey, SparkPost)('us@email.com', 'them@email.com', title, body)

.then((data) => console.log('success'))

.catch((err) => console.error('failure', err));

Or we can delay execution:

const execute = sparkPostEmailCommand(myApiKey);

// do some work

execute('us@email.com', 'them@email.com', title, body);

Why Use Higher Order Functions?

While classes in other languages would give you some type hinting and safety, the same isn’t true in JavaScript. You can’t really know that all the signatures for your execute methods all look the same unfortunately.

And without the typing, there’s no real need to add all the bloat that comes with the class implementation. Why worry about the this binding? Or the extends BaseCommand ?

The point of using these design patterns isn’t that they need to be object oriented. It’s that they help you organize your code using common patterns. Building an object with dependencies and then doing something is a common pattern, where “doing something” may be done in different ways.

If your team is used to classes and objects, then use classes and objects.

If your team is used to functional programming, then know that design patterns like the Command pattern can be used in a functional way as well.

Ivan Montiel is the founder and CEO of Clarity Hub — a company that integrates with Intercom to give your Customer Success team real-time suggestions when helping customers.

You can follow him on Twitter.

.

Tags