Strategy Pattern — What It Is And How To Use It?

A few months ago, I was in an interview and I was asked a design problem. The problem was asked to check my OOP skills and a general understanding of how does a person thinks on an abstract requirement. I blew my interview and just could not think of any better solutions because I was not aware of the strategy pattern. Here is the problem and let’s see how we can approach it using a strategy pattern.

Problem

You have an API that returns data. As a backend engineer, you need to store it in multiple different types of databases. The data can be of any type. It can be a structured, unstructured, a text file, image, pdf, or even a video file. How will you design your code such that it meets all the coding standards — Easy to use and maintain, and clean code.

Let’s understand the question in more detail. For example, when you register your profile on Instagram, you send the data to the server. The backend takes the information and stores it in different types of databases as each database has its own benefits and drawbacks. Each database stores information differently. Each of them has a different syntax for the insert queries and different connection strings to establish connections with them.

Design Principle: Separate what changes from what stays the same.

Let’s define an interface that declares methods that are specific to databases. And then we’ll create separate classes for each type of storage that implements the interface that we’ve defined. Here’s the code.

interface Datastore {
public boolean initiateConnection();
public Response insertData(data);
}
class MySQL implements Datastore {

public boolean initiateConnection(){
// Code responsible for initiating connection with MySQL
}
public Response insertData(Data data){
// Insert query to add data into MySQL
}
}
class Cassandra implements Datastore {
public boolean initiateConnection(){
// Code responsible for initiating connection with Cassandra
}
public Response insertData(Data data){
// Insert query to add data into Cassandra
}
}
class HDFS implements Datastore {
public boolean initiateConnection(){
// Code responsible for initiating connection with HDFS
// to store image or video file.
}
public Response insertData(Data data){
// Insert query to add data into HDFS
}
}

Now let’s see how we can leverage the power of strategy pattern. Let’s define the client that will be responsible for inserting the data into the respective datastore.

class Client {     Datastore datastore;
// Constructor
Client(String storageType) {
switch(storageType) {
case "MySQL":
datastore = new MySQL(data); break;
case "Cassandra":
datastore = new Cassandra(data); break;
case "HDFS":
datastore = new HDFS(data); break;
}
}

public Response insertData(Data data){
boolean connectionOK = datastore.initiateConnection();
if(connectionOK){
return datastore.insertData(data);
}
}

The client creates concrete instances of the datastore in which we want to insert the data. It holds a reference of type interface and its concrete type will be determined at the runtime. Now let’s use the client to insert the data that we’ve got from the API.

// Call the getData API and get the response
Data data = getData();
// Create client
Client client = new Client("MySQL");
client.insertData(data);

That’s how easy it is! As an application developer, all I have to care about is to just create a client and pass the type of database in which I want to insert my data. The client will handle all the nitty-gritty of establishing the connection and inserting the data into a specific database. How amazing it is!

Let’s take a look at the definition of strategy pattern according to the Head First Design Pattern book.

“The strategy pattern defines a family of algorithms, encapsulates each one , and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”

Here in our case, methods specific to databases are the family of algorithms that we have encapsulated in an interface. We have defined interface variable that holds a reference to a specific behavior at runtime. Based on the concrete database type it will invoke methods specific to the database polymorphically. Let’s have a look at when strategy pattern can be useful in real life scenarios.

Whenever you want ‘something’ in your application. But ‘something’ is not defined yet, or it can change in future. Or similar kind of feature may add in the future. That’s the time it can be sensed that strategy pattern will help us to write beautiful code, separate layers of concern and promote code re-usablility.

For instance, assume that we are building a game, we want to add a feature to a player to be able to move. Player can either walk or run. But in future we might want to add other features like swim or fly. We can apply strategy pattern here, by separating move behaviour of a player form the player class and encapsulate it in an interface.

I’ll leave the above exercise for the reader. Give it a try and comment down, how you will implement it!

Thanks for reading,

Happy Coding!

Let’s be an awesome developer together! I will be learning the tech and sharing it with the community.