Wednesday, June 12, 2013

GoF patterns: Template Method

Definition: This pattern defines the skeleton of an algorithm and let subclasses define some steps of it without changing the original algorithm structure.


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 to do get the final price of the ticket depending on the age (and have a whole bunch of nested IF statements in the future), the ticket buying process will have the skeleton of the buying process and the step to get the final price of the ticket will be delegated to different classes. As we said, today we just have 2 different calcs of the discount but tomorrow we may have a lot more:



TicketServiceAbstract
public abstract class TicketServiceAbstract {
 
 public double buyTicket(Client client) {
  double generalTicketPrice = 50;
  double realTicketPrice = this.calculateDiscount(generalTicketPrice);
  return realTicketPrice;
 }

 protected abstract double calculateDiscount(double generalTicketPrice);

}

HighDiscountTicketService
public class HighDiscountTicketService extends TicketServiceAbstract {

 @Override
 protected double calculateDiscount(double generalTicketPrice) {
  return generalTicketPrice - 30;
 }

}

LowDiscountTicketService
public class LowDiscountTicketService extends TicketServiceAbstract {

 @Override
 protected double calculateDiscount(double generalTicketPrice) {
  return generalTicketPrice - 10;
 }

}

And this is an invocation example:

TemplateMethodPatternTest
public class TemplateMethodPatternTest {
 
 @Test
 public void templateMethodKidPatternTest() {
  
  //A new Client is created
  Client kid = new Client();
  kid.setAge(16);
  
  HighDiscountTicketService discountService = new HighDiscountTicketService();
  
  double ticketPriceForKid = discountService.buyTicket(kid);
  Assert.assertTrue(ticketPriceForKid == 20);
 }
 
 @Test
 public void templateMethodAdultPatternTest() {
  
  //A new Client is created
  Client kid = new Client();
  kid.setAge(25);
  
  LowDiscountTicketService discountService = new LowDiscountTicketService();
  
  double ticketPriceForKid = discountService.buyTicket(kid);
  Assert.assertTrue(ticketPriceForKid == 40);
 }
 
}

The Template Method pattern is similar to the Strategy pattern, in this post you can see this same example implemented with the Strategy pattern. The main difference between both patterns is that the Strategy changes its behaviour at runtime, while the Template Method changes its behaviour at compile time.

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);