avatarPeterson C

Summary

The provided content discusses the Strategy design pattern, illustrating how it allows for interchangeable algorithms to be selected at runtime, particularly demonstrated through a user authentication example with multiple social media providers.

Abstract

The Strategy design pattern is presented as a method to enable algorithms to be switched at runtime, promoting flexibility, reusability, and maintainability in software design. This pattern is particularly useful when multiple algorithms for a given strategy are required. The article uses a real-world scenario of an application that authenticates users via various social media platforms—Facebook, Google, LinkedIn, and Instagram—to demonstrate the implementation of the Strategy pattern. It includes defining a common AuthenticationProvider interface with login and logout methods, creating concrete implementations for each provider, and using a ContextAuthenticator class to switch between these algorithms at runtime. The example concludes with a simulation of the authentication process, showcasing the ability to easily switch authentication methods without altering the context, adhering to the Open-Closed Principle of S.O.L.I.D. principles.

Opinions

  • The author emphasizes the importance of the Strategy pattern for maintaining a flexible and maintainable codebase.
  • The use of a real-world scenario for user authentication is suggested to be an effective way to understand and apply the Strategy pattern.
  • The article suggests that the Strategy pattern aligns with the Open-Closed Principle by allowing the addition of new algorithms without modifying existing code.
  • The pattern is presented as a preferable alternative to class extension, promoting the use of interfaces and composition over inheritance.

A GUIDE TO SOFTWARE DESIGN PATTERNS

Behavioral Design Pattern: Strategy

Photo by JESHOOTS.COM on Unsplash

The strategy pattern enables algorithms to switch at runtime. In plain English, that simply means deciding at runtime which concrete implementation of an interface to use using a context, which is where the actual concrete implementation injection happens. Deferring the decision on which algorithm to use until runtime allows more flexibility, reusability. It also makes the codebase more maintainable

This pattern comes in handy when multiple algorithms for a given strategy (interface) is needed. including the client, implementation of this pattern requires four components:

  1. Client -> where the context gets used
  2. Context -> where an algorithm is selected at runtime
  3. Strategy -> interface
  4. Algorithms -> concrete implementation

I’ve always found it easier to grasp new concepts using real-world scenarios. Let’s use one to see how we can how we implement it using this pattern.

Let’s say you’re part of a team that’s creating an application that does something cool. One of the must-have requirement is to authenticate each user before allowing them to use your application. People don’t generally like having to sign up for a new account. Your team decides to use a few providers such as Facebook, Google, LinkedIn, and Instagram. The strategy needs to provide two operations: login and logout. Let’s get started.

First, let’s create a strategy.

public interface AuthenticationProvider {
    void login(String username, String password);
    void logout();
}

In the algorithms, we’ll only print a message to simulate signing in and out. Now let’s create an algorithm for each provider.

Facebook:

public class FacebookAuthenticator implements AuthenticationProvider {
    @Override
    public void login(String username, String password) {
        System.out.println("Authenticating with facebook...");
    }
    @Override
    public void logout() {
        System.out.println("Logging out with facebook...");
    }
}

Google:

public class GoogleAuthenticator implements AuthenticationProvider {
    @Override
    public void login(String username, String password) {
        System.out.println("Authenticating with google...");
    }
    @Override
    public void logout() {
        System.out.println("Logging out with google...");
    }
}

Instagram:

public class InstagramAuthenticator implements AuthenticationProvider {
    @Override
    public void login(String username, String password) {
        System.out.println("Authenticating with instagram...");
    }
    @Override
    public void logout() {
        System.out.println("Logging out with instagram...");
    }
}

LinkedIn:

public class LinkedInAuthenticator implements AuthenticationProvider {
    @Override
    public void login(String username, String password) {
        System.out.println("Authenticating with linkedIn...");
    }
    @Override
    public void logout() {
        System.out.println("Logging out with linkedIn...");
    }
}

Now we need to implement the context where the algorithm switch happens.

public class ContextAuthenticator {
    
    private AuthenticationProvider strategy;
    ContextAuthenticator(AuthenticationProvider strategy){
        this.strategy = strategy;
    }
    public void login(String username, String password){
        strategy.login(username, password);
    }
    
    public void logout(){
        strategy.logout();
    }
}

Now that we have an authentication context and all the necessary algorithms implemented, the application now can authenticate users using any of these four authentication algorithms. We can now use the context to simulate the UI authentication selection.

public static void main(String[] args) {
    //sign in with facebook
    ContextAuthenticator context = new ContextAuthenticator(new FacebookAuthenticator());
    context.login("[email protected]", "S*s23Sd");
    context.logout();
    //sign in with google
    context = new  ContextAuthenticator(new FacebookAuthenticator());
    context.login("[email protected]", "Jkjs23*kw");
    context.logout();
    //sign in with linkedIn
    context = new  ContextAuthenticator(new LinkedInAuthenticator());
    context.login("[email protected]", "Ijks9&js@3");
    context.logout();
    //sign in with instagram
    context = new  ContextAuthenticator(new InstagramAuthenticator());
    context.login("[email protected]", "HUs&2!3*kw");
    context.logout();
}

— Output —

Authenticating with facebook...
Logging out with facebook...
Authenticating with facebook...
Logging out with facebook...
Authenticating with linkedIn...
Logging out with linkedIn...
Authenticating with instagram...
Logging out with instagram...

You can add as many algorithms as you wish and your context doesn’t have to change as long as they implement your strategy (interface). If you are familiar with the S.O.L.I.D principle, think of this pattern as the O (open-closed principle a.k.a OCP) with one major difference. Instead of extending a class, you implement a strategy.

This is, in a nutshell, the strategy pattern. Thank you for making it to the end. Happy coding.

Previous: Behavioral Design Pattern: State

Next: Structural Design Pattern: Facade

Programming
Software Development
Software Engineering
Design Patterns
Software Architecture
Recommended from ReadMedium