Strategy Design Pattern In Java

Narendra Koli
Javarevisited
Published in
6 min readMar 24, 2024

Strategy Design Pattern: Mastering Behavioral Flexibility

Have you ever faced a problem where you needed to switch algorithms based on changing requirements dynamically?

In this blog, we’ll explore the Strategy Pattern through a lively, conversational exchange between a curious coder and a guiding mentor.

Conversation Starts:

Curious Coder: Hi there, Guiding mentor. Could you please explain the Strategy design pattern to me?

Guiding Mentor: Sure. Let’s take an example: Forget about the Strategy pattern for a moment. Imagine you are developing software for an e-commerce application and have a class called Checkout. This class does the checkout, but before that, it has to process the payment.

Now, clients can use different payment methods, such as Credit cards, Debit cards, UPI payments, and Wallets.

You should write code for this first, and then we will see if we can improve it further.

Curious Coder: Yes. I will come back to you.

A few moments later:

See what I have implemented.

public class Main {

public static void main(String[] args) {
Checkout checkout = new Checkout();
checkout.processCheckout(PaymentType.CREDIT_CARD);
checkout.processCheckout(PaymentType.UPI);
}
}

enum PaymentType{
CREDIT_CARD,
DEBIT_CARD,
UPI,
WALLET
}

class Checkout{

void processCheckout(PaymentType paymentType){

if(paymentType == PaymentType.CREDIT_CARD){
System.out.println("Processing payment using " + PaymentType.CREDIT_CARD);
}
else if(paymentType == PaymentType.DEBIT_CARD){
System.out.println("Processing payment using " + PaymentType.DEBIT_CARD);
}
else if(paymentType == PaymentType.UPI){
System.out.println("Processing payment using " + PaymentType.UPI);
}
else if(paymentType == PaymentType.WALLET){
System.out.println("Processing payment using " + PaymentType.WALLET);
}

// Logic to check out once payment is successful.

System.out.println("Checkout process completed");
}
}

Output:

Guiding Mentor: Ok, Great.

So, I see a couple of problems in your code.

  • You need to update the Checkout class to introduce a new payment method.
  • It violates the Single Responsibility Principle — the checkout process is responsible for checkout and payment processing.
  • There are too many, if else, depending on payment methods.

In short, it takes a lot of work to maintain.

Curious Coder: How can I improve this?

Guiding Mentor: Imagine you have a Checkout class, and the payment method is plug-and-play to the Checkout class. This means you set the payment method before checkout, and their respective classes handle all the payment processes.

You want to introduce a new payment method tomorrow. Add a new payment class and plug it into the Checkout class.

If you want to delete any payment method, you must delete its class. This will not impact any other code.

How flexible is that?

Curious Coder:

Yes. That looks flexible.

But how can I write a new class that is compatible with the Checkout class?

Guiding Mentor:

This is where the interface is useful.

You create an interface called PaymentStrategy, and all payment types are responsible for implementing this interface and will implement its payment processing logic.

The Checkout class doesn’t care about how to process payment and what type of payment it is.

It simply calls the processPayment method of the PaymentStrategy interface, which calls a method of the class whose object is being referenced. (This is called Polymorphism)

Checkout class calls process payment and perform checkout.

Let’s see how we can implement this in your example:

Isn’t that simple and easy to understand?

Curious Coder: Yes. That sounds good.

Is this what is called Strategy Pattern?

Guiding Mentor:

Yes. A strategy design pattern is a behavioral design pattern that enables an algorithm’s behavior to be selected at runtime. Instead of implementing a single algorithm directly, code receives runtime instructions as to which in a family of algorithms to use.

Real-world example:

Imagine you have headphones: over-ear, On-Ear, and Noise-Cancelling. Depending on your needs, you choose and connect the right type to your phone to listen to music. Think of each headphone as a strategy for how you want to hear sound. This is like the Strategy Pattern in real life: pick the strategy that fits your needs at the moment.

Now, let’s implement the strategy pattern:

public class Main {

public static void main(String[] args) {
Checkout checkout = new Checkout();
checkout.processCheckout(new CreditCardPayment());
checkout.processCheckout(new UPIPayment());
}
}

enum PaymentType{
CREDIT_CARD,
DEBIT_CARD,
UPI,
WALLET
}

class Checkout{

void processCheckout(PaymentStrategy paymentStrategy){

// Logic to process payment now handled by implementation class
paymentStrategy.processPayment();

// Logic to check out once payment is successful.
System.out.println("Checkout process completed");
}
}

interface PaymentStrategy{
void processPayment();
}

class CreditCardPayment implements PaymentStrategy{

@Override
public void processPayment() {
System.out.println("Processing payment using " + PaymentType.CREDIT_CARD);
}

}

class UPIPayment implements PaymentStrategy{

@Override
public void processPayment() {
System.out.println("Processing payment using " + PaymentType.UPI);
}

}

class DebitCardPayment implements PaymentStrategy{

@Override
public void processPayment() {
System.out.println("Processing payment using " + PaymentType.DEBIT_CARD);
}

}

class WalletCardPayment implements PaymentStrategy{

@Override
public void processPayment() {
System.out.println("Processing payment using " + PaymentType.WALLET);
}

}

Output:

The strategy pattern has three parts:

  • Strategy interface
  • Concrete strategies
  • Context

Now, compare your initial code with this one, and you will see the difference.

Curious Coder: Now, I understand the strategy behind the Strategy design pattern. 😊

What are the benefits and drawbacks of this pattern?

Guiding Mentor:

Good question. It is important to understand the benefits and drawbacks of each design pattern instead of blindly applying patterns everywhere.

Benefits:

  • Flexibility and Reusability Different strategies can be swapped easily at runtime, depending on the context.
  • Decoupling: It helps in decoupling the algorithm implementation.
  • Ease of Explanation: We can easily add and use new strategies in existing classes without impacting other code. It’s a plug-and-play service.

Drawbacks:

  • Complexity: For simple use cases, implementing the strategy pattern might need to be more compatible with the application, introducing unnecessary abstraction layers.
  • Number of Objects: Every strategy will require an additional class. For a large number of strategies, this can lead to a proliferation of classes, increasing the complexity of the codebase.
  • Clients Must Be Aware of Strategies: The client code must know about the different strategies to choose and use, which can be considered a leak of abstraction details.

Also, remember that in our strategy pattern implementation, we passed the method parameter of type PaymentStrategy. Another way you can solve this problem is for the Checkout class to have PaymentStrategy as an instance variable, and we update the value of this variable based on payment type(Composition Relationship).

Curious Coder: Great. Again, thanks a lot for helping me improve my knowledge of design patterns.

Guiding Mentor: You are welcome.

Conversation Ends. 😊

I hope you enjoyed this article.

Thank you for dedicating a precious time to reading this blog post! 💖 🕒

If you enjoyed reading this blog post, you might also like my previous blog posts. 😊

Singleton design pattern: https://medium.com/javarevisited/singleton-design-pattern-in-java-feb854dd7903n

Adapter design pattern: https://medium.com/javarevisited/adapter-design-pattern-in-java-945164d18b7f

--

--

Narendra Koli
Javarevisited

I am a Software Engineer who loves to write....❤️