I like strategy design patterns. I tried to use it as much as possible. In essence, the strategy pattern uses delegates to decouple the algorithm classes that use them.
There are several benefits to doing this. It can prevent the use of large conditional statements to determine which algorithms are used for a specific type of object. Separate the focus, thus reducing the complexity of the client while also promoting subclassing composition. It improves modularity and measurability. Each algorithm can be tested separately. Each client can simulate an algorithm. Any client can use any algorithm. They can be intermodulated. Just like Lego bricks.
To implement a strategy pattern, there are usually two participants:
The object of this policy encapsulates the algorithm.
Client (context) object, which can use any policy in a plug-and-play manner.
Here I introduce how I use the policy pattern in Javascript, how I use it to split the library into small plug-ins in a chaotic environment, and plug-and-play packages.
Functions as policies
A function provides an excellent way to encapsulate an algorithm while also being used as a strategy. Just go through a function to the client and make sure your client can call the policy.
Let's use an example to prove it. Suppose we want to create a Greeter class. All it has to do is say hello to people. We hope that the Greeter class can know different ways to say hello to people. To achieve this idea, we create different strategies for saying hello.
// Greeter is a class of object that can greet people. // It can learn different ways of greeting people through // 'Strategies.' // // This is the Greeter constructor. var Greeter = function(strategy) { = strategy; }; // Greeter provides a greet function that is going to // greet people using the Strategy passed to the constructor. = function() { return (); }; // Since a function encapsulates an algorithm, it makes a perfect // candidate for a Strategy. // // Here are a couple of Strategies to use with our Greeter. var politeGreetingStrategy = function() { ("Hello."); }; var friendlyGreetingStrategy = function() { ("Hey!"); }; var boredGreetingStrategy = function() { ("sup."); }; // Let's use these strategies! var politeGreeter = new Greeter(politeGreetingStrategy); var friendlyGreeter = new Greeter(friendlyGreetingStrategy); var boredGreeter = new Greeter(boredGreetingStrategy); (()); //=> Hello. (()); //=> Hey! (()); //=> sup.
In the example above, Greeter is the client and has three strategies. As you can see, Greeter knows how to use the algorithm, but he doesn't know anything about the details of the algorithm.
For complex algorithms, a simple function is often not satisfied. In this case, the right way is to define it according to the object.
Class as a policy
Strategy can also be a class, especially when calculations are more complex than the artificial (strategy/algorithm) used in the examples above. Using classes allows you to define an interface for each policy.
In the following example, this is confirmed.
// We can also leverage the power of Prototypes in Javascript to create // classes that act as strategies. // // Here, we create an abstract class that will serve as the interface // for all our strategies. It isn't needed, but it's good for documenting // purposes. var Strategy = function() {}; = function() { throw new Error('Strategy#execute needs to be overridden.') }; // Like above, we want to create Greeting strategies. Let's subclass // our Strategy class to define them. Notice that the parent class // requires its children to override the execute method. var GreetingStrategy = function() {}; = (); // Here is the `execute` method, which is part of the public interface of // our Strategy-based objects. Notice how I implemented this method in term of // of other methods. This pattern is called a Template Method, and you'll see // the benefits later on. = function() { return () + (); }; = function() { return "Hello, "; }; = function() { return "Goodbye."; }; // We can already try out our Strategy. It requires a little tweak in the // Greeter class before, though. = function() { return (); }; var greeter = new Greeter(new GreetingStrategy()); () //=> 'Hello, Goodbye.'
By using classes, we define a policy with anexecutemethod object. The client can implement the interface using any policy.
Also note how I created GreetingStrategy. The interesting part is the overloading of methodexecute. It is defined in the form of other functions. Now the successor subclass of the class can change specific behaviors, such as thesayHiorsayByemethod, and does not change the conventional algorithm. This pattern is called the template method and is very suitable for the strategy pattern.
Let's see what's going on.
// Since the GreetingStrategy#execute method uses methods to define its algorithm, // the Template Method pattern, we can subclass it and simply override one of those // methods to alter the behavior without changing the algorithm. var PoliteGreetingStrategy = function() {}; = (); = function() { return "Welcome sir, "; }; var FriendlyGreetingStrategy = function() {}; = (); = function() { return "Hey, "; }; var BoredGreetingStrategy = function() {}; = (); = function() { return "sup, "; }; var politeGreeter = new Greeter(new PoliteGreetingStrategy()); var friendlyGreeter = new Greeter(new FriendlyGreetingStrategy()); var boredGreeter = new Greeter(new BoredGreetingStrategy()); (); //=> 'Welcome sir, Goodbye.' (); //=> 'Hey, Goodbye.' (); //=> 'sup, Goodbye.'
GreetingStrategy creates an algorithm for a class by specifying theexecutemethod. In the above code snippet, we take advantage of this by creating a dedicated algorithm.
Without using subclasses, our Greeter still demonstrates a polymorphic behavior. There is no need to switch over different types of Greeter to trigger the correct algorithm. All this is bound to every Greeter object.
var greeters = [ new Greeter(new BoredGreetingStrategy()), new Greeter(new PoliteGreetingStrategy()), new Greeter(new FriendlyGreetingStrategy()), ]; (function(greeter) { // Since each greeter knows its strategy, there's no need // to do any type checking. We just greet, and the object // knows how to handle it. (); });
Strategy mode in multiple environments
One of my favorite examples about policy patterns is in the Real Library. Provides an easy way to handle authentication in Node. A wide range of suppliers support it (Facebook, Twitter, Google, etc.), each of which is implemented as a strategy.
This library is feasible as an npm package, and all its strategies are the same. The library users can decide which npm package to install for their unique use cases. Here is a snippet showing how it is implemented:
// Taken from var passport = require('passport') // Each authentication mechanism is provided as an npm package. // These packages expose a Strategy object. , LocalStrategy = require('passport-local').Strategy , FacebookStrategy = require('passport-facebook').Strategy; // Passport can be instanciated using any Strategy. (new LocalStrategy( function(username, password, done) { ({ username: username }, function (err, user) { if (err) { return done(err); } if (!user) { return done(null, false, { message: 'Incorrect username.' }); } if (!(password)) { return done(null, false, { message: 'Incorrect password.' }); } return done(null, user); }); } )); // In this case, we instanciate a Facebook Strategy (new FacebookStrategy({ clientID: FACEBOOK_APP_ID, clientSecret: FACEBOOK_APP_SECRET, callbackURL: "/auth/facebook/callback" }, function(accessToken, refreshToken, profile, done) { (..., function(err, user) { if (err) { return done(err); } done(null, user); }); } ));
The library is equipped with only one or two simple authentication mechanisms. Besides that, it does not have more than one policy class interface that conforms to the context object. This mechanism allows its users to easily implement their own authentication mechanism without adversely affecting the project.
Reflection
The policy pattern provides a way for your code to increase modularity and measurability. This does not mean that (the policy pattern) is always valid. Mixins can also be used to perform functional injection, such as an object's algorithm at runtime. Flat old-fashioned duck-typing polymorphism can sometimes be simple enough.
However, using policy patterns allows you to scale up your code as load-type grows without introducing large systems at the beginning. As we saw in the example, it will be more convenient for maintenance personnel to add additional strategies in the future.