Strategy Design Pattern

The strategy pattern is a behavioral design pattern that enables clients to choose an algorithm from a family of algorithms at run-time and gives it a simple way to access it.

Strategy Pattern

The strategy pattern is a behavioral design pattern that enables clients to choose an algorithm from a family of algorithms at run-time and gives it a simple way to access it.

Essentially, the strategy pattern allows us to change the behavior of an algorithm at runtime. Typically, we would start with an interface to apply algorithms and then implement it multiple times for each possible algorithm.

Related definitions of strategy design principle

Before we dig deep into a Strategy design pattern, here are some definitions related to Facade design patterns

Context: This class contains a property to hold the reference of a strategy object. This property will be set at run-time according to the required algorithm. In simple words, the context class in this diagram represents the class that uses the algorithm.

Strategy: Strategy is “A set of encapsulated algorithms (read it as classes) which behave independently of the client using it.”. Strategy can be an interface or an abstract class. The idea here is to make the strategy behave as polymorphic. In it, all the operations the strategies need to implement are defined.

Concrete Strategy: It represents the concrete strategies, which inherits from strategy.

Pros and cons of strategy design principle

Pros

  • Provides an alternative to subclassing the context class to get a variety of algorithms or behaviors
  • Eliminates significant conditional statements.
  • Provides a choice of implementations for the same behavior

Cons

  • Increases the number of objects
  • All algorithms use the same strategy interface

Benefits of strategy design principle

Use strategy pattern whenever

  • Three are multiple strategies for a given problem, and the selection criteria of a strategy are defined at run-time.
  • An algorithm uses data that clients shouldn’t know about. Use the strategy pattern to avoid exposing a complex, algorithm-specific data structure.
  • A class defines many behaviors, which appear as multiple conditional statements in its operations. Instead of many conditionals, move related conditional branches into their strategy class.

Strategy design principle Java example

Let’s assume you are implementing a payment module and customers are placed in either of three categories

  • Bronze customers: Get a 4% discount
  • Silver customers: Get a 6% discount
  • Gold customers: Get an 8% discount

Even in this simplified example, it is possible to identify three serious design problems with this approach.

  • Conditional Logic: The solution above is based on conditional logic, introducing maintenance challenges. In case
  • Coupling: The client of the function above passes a flag used to control the inner logic of the function. According to Page-Jones, this kind of control-coupling isn’t the problem, but this often indicates the presence of other design ills” one such design ill is the loss of encapsulation. The client of the above function knows about its internals. The knowledge of the different custom categories is spread among several modules.
  • Scalability: The solution has a design problem. In case the customer categories are extended, the inflexibility of the design forces modification to the function above. Modifying existing code is often an error-prone activity.
package com.logicalconstant;

public class SwitchCase {
    public static double calculatePrice(CustomerType type,
double totalAmount, double shipping){
        double price = 0;
        switch (type){
            case goldCustomer:
                price = (totalAmount*0.90)+shipping;
                break;
            case silverCustomer:
                price = (totalAmount*0.95)+shipping;
                break;
            case bronzeCustomer:
                price = (totalAmount*0.98)+shipping;
        }
        return price;
    }
}

The potential problem listed above may be derived from the failure to adhere to one crucial design principle: The Open Closed Principle. The principle is summarised as “Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.”

According to the open-closed principle, an ideal module’s behavior is extended by adding code instead of changing the existing source. Following this principle minimizes the risk of introducing bugs in existing, tested code and typically raises the design’s quality by introducing loose coupling.

In the example above, closing the customer module against changes to the customer categories would be suitable. That means we add a new class whenever we add new customer categories rather than changing the existing style.

The strategy provides a way to follow and reap the benefit of the open-closed principle.

Strategy Pattern Example

In this example, the Customer does not have any knowledge about the concrete categories. The concrete strategy is typically assigned to the context by the application. All price calculations are delegated to the given strategy.

Using this pattern, the interface for calculating price adheres to the open-closed principle: It is possible to add or remove categories without modifying the existing calculation algorithms or the customer.

For implementing the above example, A customer can get a discount. Still, there is three logic to calculate the discount based on the type of customer, i.e.,

  • the bronze customer receives a 4% discount,
  • Silver customers get a 6% discount, and
  • gold customer receives an 8% discount.

First, create an interface.

public interface CustomerPriceStrategy {
    double calculatePrice ();
}

Then time to implement the Price logic

BronzePriceStrategy.Java

public class BronzePaymentStrategy implements CustomerPriceStrategy {
    @override
    public double price (double amount) {
        
        return amount * 0.04; 
    }
}

SilverPriceStrategy.Java

public class SilverPaymentStrategy implements CustomerPriceStrategy {
    @override
    public double price (double amount) {
        
        return amount * 0.06; 
    }
}

GoldPriceStrategy.Java

public class GoldPaymentStrategy implements CustomerPriceStrategy {
    @override
    public double price (double amount) {
        
        return amount * 0.06; 
    }
}

The implementation is done, Now, we are going to test it.

public class Main {

    public static void main(String[] args) {
	 CustomerPriceStrategy strategy = new GoldPriceStrategy();
     double a = strategy.calculatePrice(1200);
        System.out.println(a);
        strategy = new SilverPriceStrategy();
        a = strategy.calculatePrice(122);
        System.out.println(a);
    }
}

Related Articles