Monday, June 3, 2013

GoF patterns: Strategy

Definition: This pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime. This pattern is used when you have some logic and you want it to vary independently of the client who uses it while still having an extensible model.



Why encapsulated? Because each algorithm exists in a different class and access all that logic with a method. Why Interchangeable? Because each of those classes implement the same interface.

Say for example, I have a service for buying tickets and I want the service to provide discounts depending on the age of the person. I want to have 2 kind of discounts, but I am sure there will be a lot more in the future. Instead of having an IF statement (and have a whole bunch of nested IF statements in the future), in the whole process of buying a ticket, the strategy will be implemented in the calculus of the discount. Why? Because the result of the calculus is the same (a new price on the ticket) but the way it works is different from each other. As we said, today we just have 2 different calcs of the discount but tomorrow we may have a lot more:



TicketService
public class TicketService {
 
 private DiscountStrategy discountProcess;
 
 public double buyTicket(Client client) {
  double generalTicketPrice = 50;
  double realTicketPrice = discountProcess.calculateDiscount(generalTicketPrice);
  return realTicketPrice;
 }

}

These are the different strategies we have:

HighDiscountStrategy
public class HighDiscountStrategy implements DiscountStrategy {

 @Override
 public double calculateDiscount(double ticketPrice) {
  return ticketPrice - 30;
 }

}

LowDiscountStrategy
public class LowDiscountStrategy implements DiscountStrategy {

 @Override
 public double calculateDiscount(double ticketPrice) {
  return ticketPrice - 10;
 }

}

As you can see, our buyTicket method doesn't care about which type of discount it is applying.

Now, the question is, when do we set the type of discount? Maybe in the service constructor?
But, what if we just want one service class to be instantiated and use that same instance with every client that arrives? Well, that really depends on the problem to be solved, the Strategy pattern doesn't tell you when and how to set the discount type... In this case, I chose to create a new service for each client. So, passing the client in the constructor is enough.

So, our class will be like this with the constructor:

TicketService
public class TicketService {
 
 private DiscountStrategy discountProcess;
 
 public TicketService(Client client) {
  if (client.getAge() >= 18) {
   discountProcess = new LowDiscountStrategy();
  } else {
   discountProcess = new HighDiscountStrategy();
  }
 }
 
 public double buyTicket(Client client) {
  double generalTicketPrice = 50;
  double realTicketPrice = discountProcess.calculateDiscount(generalTicketPrice);
  return realTicketPrice;
 }

}

And an invocation example:

//A new Client is created
  Client kid = new Client();
  kid.setAge(16);
  
  //The service to create discounts is created and its behaviour changes according to the
  //age of the client
  TicketService discountService = new TicketService(kid);
  
  double ticketPriceForKid = discountService.buyTicket(kid);
  Assert.assertTrue(ticketPriceForKid == 20);
  
  //A new Client is created
  Client adult = new Client();
  adult.setAge(25);
  
  //The service to create discounts is created and its behaviour changes according to the
  //age of the client
  TicketService discountService = new TicketService(adult);
  
  double ticketPriceForAdult = discountService.buyTicket(adult);
  Assert.assertTrue(ticketPriceForAdult == 40);

No comments:

Post a Comment