Strategy design pattern using Lambdas

I was recently reviewing some code and was thinking of solving a code smell that I came across. It seemed to me that the Strategy pattern would be a good fit to solve the code smell. Well, honestly , I am not a big fan of design patterns in every code smell but I feel they can surely be used in cases where it is appropriate. I consider design patterns as a common vocabulary to solve problems.

I don’t remember the exact implementation of most of the design patterns at the back of my head and so I started reading about the Strategy pattern on wiki :  Strategy pattern on wiki. If you are not aware of the pattern at all, I would recommend that you read  about the pattern first before looking at the Java code on wiki.

The problem the code sample tries to solve is using a billing strategy at runtime to generate  the Customers bill. Customer class can use a NormalStrategy or use a HappyHourStrategy. The code is open for extension in case we decide to use a third strategy to bill the customer.

At  the  end of the Java code sample, the wiki page mentions that there is a much simpler example in modern java using Lambdas. When I clicked that link, it showed an altogether different example. Well, I got a little curious and decided to solve the same Customer bill generation problem using Lambdas.

Thought process:

The strategy pattern has an interface and the example above uses 2 concrete implementations of the strategy. If I could replace it with a lambda, what do I need to focus on ? The 2 implementations of the strategy are quite simple, what do they do ? They take a double as a parameter and return a double. Can I represent this using a lambda ? Yes, I can. There is a functional interface in Java 8 called Function, it takes one type as input and returns/produces another type:

public interface Function<T, R>

And so we can do this :

private Function<Double, Double> billingStrategy;

Refer to the code on wiki, Customer constructor takes a BillingStrategy:

public Customer(BillingStrategy billingStrategy){
             this.billingStrategy = billingStrategy;
}

This can now be replaced :

public Customer(Function<Double, Double> billingStrategy){
             this.billingStrategy=billingStrategy;
}

The add method in the Customer class is:

public void add(double price, int quantity){
     drinks.add(billingStrategy.getActualPrice(price * quantity));
}

Instead of the implementation of the billing strategy, we now have a billingStrategy which is a Functional interface(Function)

public void add(double price, int quantity){
       drinks.add(billingStrategy.apply(price * quantity));

And so now the full code:

Customer.java


import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class Customer {
private List<Double> drinks = new ArrayList<>();
private Function<Double, Double> billingStrategy;
public Customer(Function<Double, Double> billingStrategy){
this.billingStrategy=billingStrategy;
}
public void add(double price, int quantity){
drinks.add(billingStrategy.apply(price * quantity));
}
public void printBill(){
double sum = 0;
for(Double drink: drinks){
sum+=drink;
}
System.out.println("Total Bill : "+sum);
}
public void setStrategy(Function<Double, Double> billingStrategy){
this.billingStrategy=billingStrategy;
}
}

StrategyPatternWiki.java


import java.util.function.Function;
public class StrategyPatternTest {
public static void main(String[] args) {
Function<Double, Double> normalStrategy = (Double d) -> d;
Function<Double, Double> happyHourStrategy = (Double d) -> d * 0.5;
Customer firstCustomer = new Customer(normalStrategy);
// Normal billing
firstCustomer.add(1.0, 1);
// Start Happy Hour
firstCustomer.setStrategy(happyHourStrategy);
firstCustomer.add(1.0, 2);
// New Customer
Customer secondCustomer = new Customer(happyHourStrategy);
secondCustomer.add(0.8, 1);
// The Customer pays
firstCustomer.printBill();
// End Happy Hour
secondCustomer.setStrategy(normalStrategy);
secondCustomer.add(1.3, 2);
secondCustomer.add(2.5, 1);
secondCustomer.printBill();
}
}

What has changed:

  1. The billing strategy has been declared using a Function and lambda.
    • The class NormalStrategy takes a rawPrice and returns the rawPrice and hence we can do this:                                                                                                              Function<Double, Double> normalStrategy =  (Double d) -> d;
    • The HappyHourStrategy takes a rawPrice and gives a 50 % discount and hence we can do this :                                                                                            Function<Double, Double> happyHourStrategy =  (Double d) -> d * 0.5;
  2. This is being passed to the Customer constructor.
  3. The setStrategy method now takes a Function<Double,Double>

Notice that we have no reference to the BillingStrategy interface or the 2 classes which implement the BillingStrategy interface.

Using lambdas and the functional interface helped us replacing the Strategy pattern which defines an interface and different classes which implement the interface. We simply used functions, not normal functions but higher order functions. The alternative way of solving the same problem using lambdas and functional interfaces is quite powerful.

We could do better:

BillingStrategy.java


public class BillingStrategy {
public static Double getNormalStrategy(Double d){
return d;
}
public static Double getHappyHourStrategy(Double d){
return d * 0.5;
}
}

Notice that this is a class with static methods. It has behaviour for the 2 strategies

StrategyPatternTest.java


public class StrategyPatternTest {
public static void main(String[] args) {
Function<Double, Double> normalStrategy = BillingStrategy::getNormalStrategy;
Function<Double, Double> happyHourStrategy = BillingStrategy::getHappyHourStrategy;
Customer firstCustomer = new Customer(normalStrategy);
// Normal billing
firstCustomer.add(1.0, 1);
// Start Happy Hour
firstCustomer.setStrategy(happyHourStrategy);
firstCustomer.add(1.0, 2);
// New Customer
Customer secondCustomer = new Customer(happyHourStrategy);
secondCustomer.add(0.8, 1);
// The Customer pays
firstCustomer.printBill();
// End Happy Hour
secondCustomer.setStrategy(normalStrategy);
secondCustomer.add(1.3, 2);
secondCustomer.add(2.5, 1);
secondCustomer.printBill();
}
}

The code change is  the usage of the method references.

Function<Double, Double> normalStrategy = BillingStrategy::getNormalStrategy;
Function<Double, Double> happyHourStrategy = BillingStrategy::getHappyHourStrategy;

Well it looks like Java 8 lambdas, methods references and the functional style of programming have opened up new and really interesting ways of solving recurring problems in our code.The next time you encounter a Strategy pattern, think of lambdas and the Functional Interface, Function.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: