Strategy

The Strategy pattern enables an object, called the Context, to support variations in its logic by extracting the variable parts into separate, interchangeable objects called Strategies. The context implements the common logic of a family of algorithms, while a strategy implements the mutable parts, allowing the context to adapt its behavior depending on different factors such as an input value, a system configuration, or user preferences. The strategies are usually part of a family of solutions and all of them implement the same interface, which is the one that is expected by the context. The following figure shows the situation we just described:

Strategy

The preceding figure shows how the context object can plug different strategies into its structure, as they were replaceable parts of a piece of machinery. Imagine a car, its tires can be considered its strategy to adapt to the different road conditions. We can fit the winter tires to go on snowy roads thanks to their studs, while we can decide to fit high- performance tires to go mainly on motorways for a long trip. On the one hand, we don't want to change the entire car for this to be possible, and on the other, we don't want a car with eight wheels so that it can go on every possible road.

We quickly understand how powerful this pattern is; not only it helps with separating the concerns within an algorithm but it also enables it to have a better flexibility and adapt to different variations of the same problem.

The Strategy pattern is particularly useful in all those situations where supporting variations of an algorithm requires complex conditional logic (lots of if-else or switch statements) or mixing together different algorithms of the same family. Imagine an object called Order that represents an online order of an e-commerce website. The object has a method called pay() that, as it says, finalizes the order and transfers the funds from the user to the online store.

To support different payment systems, we have a couple of options as follows:

In the first solution, our Order object cannot support other payment methods unless its code is modified. Also, this can become quite complex when the number of payment options grows. Instead, using the Strategy pattern enables the Order object to support a virtually unlimited number of payment methods and keeps its scope limited to only managing the details of the user, the purchased items, and relative price, while delegating the job of completing the payment to another object.

Let's now demonstrate this pattern with a simple, realistic example.

Let's consider an object called Config that holds a set of configuration parameters used by an application, such as the database URL, the listening port of the server, and so on. The Config object should be able to provide a simple interface to access these parameters but also a way to import and export the configuration using a persistent storage, such as a file. We want to be able to support different formats to store the configuration, as for example, JSON, INI, or YAML.

By applying what we learned about the Strategy pattern, we can immediately identify the variable part of the config object, which is the functionality that allows us to serialize and deserialize the configuration. This is going to be our strategy.

Let's create a new module called config.js and let's define the generic part of our configuration manager:

In the preceding code, we encapsulate the configuration data into an instance variable and then we provide the set() and get() methods that allow us to access the configuration properties using a dotted path notation (for example, property.subProperty) by leveraging a npm library called object-path (https://npmjs.org/package/object-path). In the constructor, we also take in a strategy as input, which represents an algorithm for parsing and serializing the data.

Let's now see how we are going to use strategy, by writing the remaining part of the Config class:

Config.prototype.read = function(file) {
  console.log('Deserializing from ' + file);
  this.data = this.strategy.deserialize(fs.readFileSync(file, 'utf-8'));
}

Config.prototype.save = function(file) {
  console.log('Serializing to ' + file);
  fs.writeFileSync(file, this.strategy.serialize(this.data));
}
module.exports = Config;

In the previous code, when reading the configuration from a file, we delegate the deserialization task to the strategy; then, when we want to save the configuration into a file, we use strategy to serialize the configuration. This simple design allows the Config object to support different file formats when loading and saving its data.

To demonstrate this, let's create a couple of strategies into a file called strategies.js. Let's start with a strategy for parsing and serializing JSON data:

Nothing really complicated! Our strategy simply implements the agreed interface, so that it can be used by the Config object.

Similarly, the next strategy we are going to create allows us to support the INI file format:

Now, to show you how everything comes together, let's create a file named configTest.js and let's try to load and save a sample configuration using different formats:

Our test module reveals the properties of the Strategy pattern. We defined only one Config class, which implements the common parts of our configuration manager, while changing the strategy used for serializing and deserializing allowed us to create different Config instances supporting different file formats.

The preceding example shows only one of the possible alternatives that we had for selecting the strategy. Other valid approaches might have been the following:

As we can see, there are several options for selecting the strategy to use and the right one only depends on our requirements and the trade-off in terms of features/simplicity we want to obtain.

Also, the implementation of the pattern itself can vary a lot, for example, in its simplest form, the context and the strategy can both be simple functions:

Even though the preceding situation might seem insignificant, it should not be underestimated in a programming language, such as JavaScript, where functions are first-class citizens and used as much as full-fledged objects.

Between all these variations though, what does not change is the idea behind the pattern, as always the implementation can slightly change but the core concepts that drive the pattern are always the same.

Passport.js (http://passportjs.org) is an authentication framework for Node.js which allows to add support for different authentication schemes into a web server. With Passport, we can provide a Login with Facebook or Login with Twitter functionality to our web application with minimal effort. Passport uses the Strategy pattern to separate the common logic required during an authentication process from the parts that can change, namely the actual authentication step. For example, we might want to use OAuth in order to obtain an accessToken to access a Facebook or Twitter profile, or simply use a local database to verify a username/password pair. For Passport, these are all different strategies for completing the authentication process, and as we can imagine, this allows the library to support a virtually unlimited number of authentication services. Take a look at the number of different authentication providers supported at http://passportjs.org/guide/providers, to get an idea of what the Strategy pattern can do.