What is it ?
Design pattern is an elegant solution to a problem that recurs.
STRATEGY
When ?
The Strategy pattern is very useful when we have a similar set of algorithms, and we need to switch between them in different pieces of the application.
Diagram :
Example
Problem : Calculate different types of taxes.
Solution :
public class Invoice { private double value; public Invoice(double value) { this.value = value; } public double getValue() { return value; } private void setValue(double value) { this.value = value; } }
public interface Tax { double calculate(Invoice invoice); }
public class FederalTax implements Tax { @Override public double calculate(Invoice invoice) { return invoice.getValue() * 0.06; } }
public class CityTax implements Tax { @Override public double calculate(Invoice invoice) { return invoice.getValue() * 0.1; } }
// CODE TEST public class StrategyTest { @Test public void calulateTaxTest() { Double price = 100.0; CityTax cityTax = new CityTax(); FederalTax federalTax = new FederalTax(); Invoice invoice = new Invoice(price); Double total = cityTax.calculate(invoice) + federalTax.calculate(invoice); Double result = 16.0; TestCase.assertEquals(total, result); } }
CHAIN OF RESPONSIBILITY
When ?
When we have a defined chain of behaviors that can be applied according to specific scenarios.
Diagram :
Example :
Problem : Apply distinct discounts in a order . Rules :
Apply discount if :
- order value > 500 = 10%
- order quantity itens > 5 = 20 % ( but the order value need be more than 500 )
Solution :
public class Item { private String name; private double value; public Item(String name, double value) { this.name = name; this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getValue() { return value; } public void setValue(double value) { this.value = value; } }
public class Order { private List<Item> itens = new ArrayList<>(); public Order() {} public double getValue() { return this.getItens().stream() .mapToDouble(i -> i.getValue()) .sum(); } public List<Item> getItens() { return Collections.unmodifiableList(itens); } public void addItem(Item item) { itens.add(item); } }
public interface Discount { double applyDiscount(Order order); void callNext(Discount next); }
public class DiscountFiveItens implements Discount { private Discount next; @Override public double applyDiscount(Order order) { if(order.getItens().size() > 5 && order.getValue() > 500) { return order.getValue() * 0.2; } else { return next.applyDiscount(order); } } @Override public void callNext(Discount next) { this.next = next; } }
public class DiscountOrderBiggerThan500 implements Discount { private Discount next; @Override public double applyDiscount(Order order) { if(order.getValue() > 500) { return order.getValue() * 0.1; } else { return next.applyDiscount(order); } } @Override public void callNext(Discount next) { this.next = next; } }
public class WhitoutDiscount implements Discount { @Override public double applyDiscount(Order order) { return 0; } @Override public void callNext(Discount next) { // Don' have the next one. } }
public class DiscountCalculator { public double calculate(Order order) { Discount d1 = new DiscountFiveItens(); Discount d2 = new DiscountOrderBiggerThan500(); Discount d3 = new WhitoutDiscount(); d1.callNext(d2); d2.callNext(d3); return d1.applyDiscount(order); } }
// CODE TEST public class ChainOfResponsabilityTest { @Test public void testWithoutDiscount(){ DiscountCalculator calculator = new DiscountCalculator(); Order order = new Order(); order.addItem(new Item("ITEM A", 250.0)); order.addItem(new Item("ITEM B", 250.0)); double discount = calculator.calculate(order); double expectedResult = 0.0; TestCase.assertEquals(discount, expectedResult); } @Test public void testDiscountFiveItensLessThan500(){ DiscountCalculator calculator = new DiscountCalculator(); Order order = new Order(); order.addItem(new Item("ITEM 1", 10.0)); order.addItem(new Item("ITEM 2", 10.0)); order.addItem(new Item("ITEM 3", 10.0)); order.addItem(new Item("ITEM 4", 10.0)); order.addItem(new Item("ITEM 5", 10.0)); order.addItem(new Item("ITEM 6", 10.0)); double discount = calculator.calculate(order); TestCase.assertTrue(discount == 0.0); } @Test public void testDiscountFiveItensMoreThan500(){ DiscountCalculator calculator = new DiscountCalculator(); Order order = new Order(); order.addItem(new Item("ITEM 1", 200.0)); order.addItem(new Item("ITEM 2", 200.0)); order.addItem(new Item("ITEM 3", 200.0)); order.addItem(new Item("ITEM 4", 200.0)); order.addItem(new Item("ITEM 6", 100.0)); order.addItem(new Item("ITEM 7", 100.0)); double discount = calculator.calculate(order); TestCase.assertTrue(discount == 200); } @Test public void testDiscountOtherBiggerThan500(){ DiscountCalculator calculator = new DiscountCalculator(); Order order = new Order(); order.addItem(new Item("ITEM A", 1000.0)); double discount = calculator.calculate(order); TestCase.assertTrue(discount == 100); } }
TEMPLATE METHOD
When ?
Basically, it will be useful when you have the same implementation for more than one strategy.
Diagram :
Example :
Problem : Calculate different types of taxes, use the same scenario of the Strategy example. Also, the taxes need following this conditions:
- CityTax :
if invoice value is more than 500 and has a item with value more than 100 the tax is 10%, otherwise is 6%.
- FederalTax :
if invoice value is more than 500 the tax is 7%, otherwise is 5%.
Solution : Look we can say which both conditions are following this rule: if the scenario attend the first condition we should apply the maximum rate, otherwise we should apply the minimum rate.
// CODE TEST public class TemplateMethodTest { @Test public void calulateCityTaxMaximumRateTest() { Invoice invoice = new Invoice(); invoice.addItem(new Item("ITEM 1", 1000.0)); Double result = new CityTax().calculate(invoice); Double resultCityTexExp = invoice.getValue() * 0.1; TestCase.assertEquals(result, resultCityTexExp); } @Test public void calulateCityTaxMinimumRateTest() { Invoice invoice = new Invoice(); invoice.addItem(new Item("ITEM 1", 100.0)); Double result = new CityTax().calculate(invoice); Double resultCityTexExp = invoice.getValue() * 0.6; TestCase.assertEquals(result, resultCityTexExp); } @Test public void calulateFederalTaxMaximumRateTest() { Invoice invoice = new Invoice(); invoice.addItem(new Item("ITEM 1", 800.0)); Double result = new FederalTax().calculate(invoice); Double resultCityTexExp = invoice.getValue() * 0.7; TestCase.assertEquals(result, resultCityTexExp); } @Test public void calulateFederalTaxMinimumRateTest() { Invoice invoice = new Invoice(); invoice.addItem(new Item("ITEM 1", 300.0)); Double result = new FederalTax().calculate(invoice); Double resultCityTexExp = invoice.getValue() * 0.5; TestCase.assertEquals(result, resultCityTexExp); } }
public class CityTax extends TemplateConditionalInvoice { @Override public boolean isToApplyMaximunRate(Invoice invoice) { return invoice.getValue() > 500 && hasItemValueMoreThan100(invoice); } private boolean hasItemValueMoreThan100(Invoice invoice) { Optional<Item> invoiceOptional = invoice.getItens().stream() .filter(i -> i.getValue() > 100) .findFirst(); return invoiceOptional.isPresent(); } @Override protected double applyMaximumRate(Invoice invoice) { return invoice.getValue() * 0.10; } @Override protected double applyMinimumRate(Invoice invoice) { return invoice.getValue() * 0.60; } }
public class FederalTax extends TemplateConditionalInvoice { @Override public boolean isToApplyMaximunRate(Invoice invoice) { return invoice.getValue() > 500; } @Override protected double applyMaximumRate(Invoice invoice) { return invoice.getValue() * 0.70; } @Override protected double applyMinimumRate(Invoice invoice) { return invoice.getValue() * 0.50; } }
public class Invoice { private List<Item> itens = new ArrayList<>(); public Invoice() {} public List<Item> getItens() { return Collections.unmodifiableList(itens); } public void addItem(Item item) { itens.add(item); } public double getValue() { return this.getItens().stream().mapToDouble(i -> i.getValue()).sum(); } }
public class Item { private String name; private double value; public Item(String name, double value) { this.name = name; this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getValue() { return value; } public void setValue(double value) { this.value = value; } }
public interface Tax { double calculate(Invoice invoice); }
— LOOK THIS WITH ATTENTION —
public abstract class TemplateConditionalInvoice implements Tax { public double calculate(Invoice invoice) { if(isToApplyMaximunRate(invoice)) { return applyMaximumRate(invoice); } else { return applyMinimumRate(invoice); } } protected abstract boolean isToApplyMaximunRate(Invoice invoice); protected abstract double applyMaximumRate(Invoice invoice); protected abstract double applyMinimumRate(Invoice invoice); }
DECORATOR
When ?
Whenever we realize we have behaviors that can be composed of behaviors involved other classes in the same hierarchy.”Behaviors comprise other behaviors.”
Diagram:
Example:
public class DecoratorTest { @Test public void testDecorator() { Tax taxComplexy = new CityTax(new FederalTax()); Invoice invoice = new Invoice(1000.0); double result = taxComplexy.calculate(invoice); TestCase.assertEquals(result, 150); } }
public abstract class Tax { private final Tax otherTax; public Tax(Tax otherTax) { this.otherTax = otherTax; } // construtor default public Tax() { this.otherTax = null; } protected double calculateOtherTax(Invoice invoice) { // If don't exist exist if(otherTax == null) return 0; return otherTax.calculate(invoice); } public abstract double calculate(Invoice invoice); }
public class FederalTax extends Tax { public FederalTax(Tax otherTax) { super(otherTax); } public FederalTax(){} @Override public double calculate(Invoice invoice) { return invoice.getValue() * 0.05 + this.calculateOtherTax(invoice); } }
public class CityTax extends Tax { public CityTax(Tax otherTax) { super(otherTax); } public CityTax(){} @Override public double calculate(Invoice invoice) { return invoice.getValue() * 0.1 + this.calculateOtherTax(invoice); } }
public class Invoice { private double value; public Invoice(double value) { this.value = value; } public double getValue() { return value; } private void setValue(double value) { this.value = value; } }
STATE
When ?
When we have actions to be performed according to the states.
Diagram :
Example:
Problem :
If the state order is in progress apply 10% of discount;
If the state order is in approved apply 5% of discount;
@Test public void testDiscountExtra(){ Order order = new Order(100.0); // Apply 10% discount because the state is Progress order.applyDiscountExtra(); TestCase.assertEquals(order.value, 90.0); // Aprove Order order.approve(); // Apply 5% discount because the state is Progress order.applyDiscountExtra(); TestCase.assertEquals(order.value, 85.5); order.finish(); // If you call now applyDiscountExtra() // you will receive a RunTimeException try{ order.applyDiscountExtra(); }catch (RuntimeException r){ TestCase.assertTrue(true); } }
public class Order { protected double value; protected OrderState currentState; // veja a mudança aqui public Order(double value) { this.value = value; this.currentState = new OrderProgress(); } public void applyDiscountExtra() { currentState.applyDiscountExtra(this); } public void approve() { currentState.approve(this); } public void refuse() { currentState.refuse(this); } public void finish() { currentState.finish(this); } public double getValue() { return value; } public void setValue(double value) { this.value = value; } public OrderState getCurrentState() { return currentState; } public void setCurrentState(OrderState currentState) { this.currentState = currentState; } }
public interface OrderState { void applyDiscountExtra(Order order); void approve(Order order); void refuse(Order order); void finish(Order order); }
public class OrderApproved implements OrderState { @Override public void applyDiscountExtra(Order order) { order.value -= order.value * 0.05; } @Override public void approve(Order order) { order.currentState = new OrderApproved(); } @Override public void refuse(Order order) { order.currentState = new OrderRefused(); } @Override public void finish(Order order) { order.currentState = new OrderFinish(); } }
ublic class OrderFinish implements OrderState { @Override public void applyDiscountExtra(Order order) { new RuntimeException("Order finished"); } @Override public void approve(Order order) { new RuntimeException("Order finished"); } @Override public void refuse(Order order) { new RuntimeException("Order finished"); } @Override public void finish(Order order) { order.currentState = new OrderFinish(); } }
public class OrderProgress implements OrderState { @Override public void applyDiscountExtra(Order order) { order.value -= order.value * 0.1; } @Override public void approve(Order order) { order.currentState = new OrderApproved(); } @Override public void refuse(Order order) { order.currentState = new OrderRefused(); } @Override public void finish(Order order) { throw new RuntimeException("Order in progress"); } }
public class OrderRefused implements OrderState { @Override public void applyDiscountExtra(Order order) { new RuntimeException("Order refused"); } @Override public void approve(Order order) { new RuntimeException("Order refused"); } @Override public void refuse(Order order) { order.currentState = new OrderRefused(); } @Override public void finish(Order order) { new RuntimeException("Order refused"); } }
Builder
When ?
Whenever we have a complex object to be created, which it has several attributes or a complicated creation logic, we can centralize all in a Builder.
Example :
Problem : Create a invoice with a complex logic.
Solution:
Result/Cosole
*******************INVOICE**********************
Invoice Date Emission :09/06/2016
Invoice Date :09/06/2016
*******************ITEMS**********************
-ITEM A $20.0
-ITEM B $25.0
-ITEM C $4.0
-ITEM D $8.5
Total Items :57.5
*******************DISCOUNTS**********************
-B2B – 10.0%
-Week Promotion – 5.0%
Total Porcentagem :15.0%
*******************OBSERVATIONS********************
Order Late
*************************************************
Total Discount: $15.0
Total Value: $48.875
Date to Pay: 14/06/2016
Code :
@Test public void builderTest(){ LocalDate yesteday = LocalDate.now().minus(1, ChronoUnit.DAYS); BuilderInvoice invoice = new BuilderInvoice(); invoice.addItem("ITEM A", 20.0) .addItem("ITEM B", 25.0) .addItem("ITEM C", 4.0) .addItem("ITEM D", 8.50) .addDiscount("B2B", 10) .addDiscount("Week Promotion", 5) .addObservations("Order Late") .changeDateOrder(yesteday) .close() .emission(); TestCase.assertFalse(true); }
public class BuilderInvoice { private Invoice invoice = new Invoice(); public BuilderInvoice() { invoice.setDate(LocalDate.now()); invoice.setOrder(new Order()); invoice.getOrder().setDate(invoice.getDate()); } public BuilderInvoice addItem(String name, double value) { invoice.getOrder().getItemList().add(new Item(name,value)); return this; } public BuilderInvoice addDiscount(String desc, double por) { invoice.getDiscountList().add(new Discount(desc,por)); return this; } public BuilderInvoice changeDateOrder(LocalDate date) { invoice.getOrder().setDate(date); return this; } public BuilderInvoice addObservations(String obs) { invoice.getOrder().setObservations(obs); return this; } public BuilderInvoice close(){ double valueDiscount = (invoice.getOrder().getTotalValue() * invoice.getTotalDisountPorcentage())/100; double totalValue = invoice.getOrder().getTotalValue() - valueDiscount; invoice.setTotalInvoice(totalValue); invoice.setFinishDate(LocalDate.now()); invoice.setDatePayment(invoice.getFinishDate().plus(5, ChronoUnit.DAYS)); return this; } public void emission(){ System.out.println("*******************INVOICE**********************"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); System.out.println("Invoice Date Emission :" + this.invoice.getFinishDate().format(formatter) ); System.out.println("Invoice Date :" + this.invoice.getDate().format(formatter) ); System.out.println("*******************ITEMS**********************"); this.invoice.getOrder().getItemList().stream().forEach(i->{ System.out.println("-" + i.getName() + " $" + i.getValue()); }); System.out.println("Total Items :" + this.invoice.getOrder().getTotalValue() ); System.out.println("*******************DISCOUNTS**********************"); this.invoice.getDiscountList().stream().forEach(i->{ System.out.println("-" + i.getDescription() + " - " + i.getPorcent() + "%"); }); System.out.println("Total Porcentagem :" + this.invoice.getTotalDisountPorcentage() + "%" ); System.out.println("*******************OBSERVATIONS********************"); System.out.println( this.invoice.getOrder().getObservations()); System.out.println("*************************************************"); System.out.println("Total Discount: $"+ this.invoice.getTotalDiscount()); System.out.println("Total Value: $"+ this.invoice.getTotalInvoice()); System.out.println("Date to Pay: "+ this.invoice.getDatePayment().format(formatter)); } }
public class Invoice { private Order order; private double totalItens; private List<Discount> discountList; private double totalInvoice; private LocalDate date; private LocalDate datePayment; private LocalDate finishDate; public Invoice() { this.discountList = new ArrayList<>(); this.discountList = new ArrayList<>(); } public LocalDate getFinishDate() { return finishDate; } public void setFinishDate(LocalDate finishDate) { this.finishDate = finishDate; } public Double getTotalDisountPorcentage(){ return this.getDiscountList().stream().mapToDouble(i->i.getPorcent()).sum(); } public List<Discount> getDiscountList() { return discountList; } private void setDiscountList(List<Discount> discountList) { this.discountList = discountList; } public LocalDate getDate() { return date; } public void setDate(LocalDate date) { this.date = date; } public LocalDate getDatePayment() { return datePayment; } public void setDatePayment(LocalDate datePayment) { this.datePayment = datePayment; } public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } public double getTotalItens() { return totalItens; } private void setTotalItens(double totalItens) { this.totalItens = totalItens; } public double getTotalDiscount() { return this.getDiscountList().stream().mapToDouble( d-> d.getPorcent()).sum(); } public double getTotalInvoice() { return totalInvoice; } public void setTotalInvoice(double totalInvoice) { this.totalInvoice = totalInvoice; } }
public class Order { private List<Item> itemList; private LocalDate date; private Double totalValue; private String observations; public Order(){ date = LocalDate.now(); this.itemList = new ArrayList<>(); } public String getObservations() { return observations; } public void setObservations(String observations) { this.observations = observations; } public List<Item> getItemList() { return itemList; } public void setItemList(List<Item> itemList) { this.itemList = itemList; } public LocalDate getDate() { return date; } public void setDate(LocalDate date) { this.date = date; } public Double getTotalValue() { return this.getItemList().stream().mapToDouble(i->i.getValue()).sum(); } private void setTotalValue(Double totalValue) { this.totalValue = totalValue; } }
public class Item { private String name; private double value; private Item() { } public Item(String name, double value) { this.name = name; this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getValue() { return value; } public void setValue(double value) { this.value = value; } }
public class Discount { private String description; private double porcent; public Discount(String description, double porcent) { this.description = description; this.porcent = porcent; } public String getDescription() { return description; } private void setDescription(String description) { this.description = description; } public double getPorcent() { return porcent; } private void setPorcent(double porcent) { this.porcent = porcent; } }
Observer
When ?
When we have several different actions to be performed after a certain process.
Diagram :
Example :
Problem : After close an invoice you need do:
- Persist in database;
- Send email;
- Send SMS;
Solution :
Result/Console:
-> Persist in the database
*******************INVOICE**********************
Invoice Date Emission :09/06/2016
Invoice Date :09/06/2016
*******************ITEMS**********************
-ITEM A $20.0
-ITEM B $25.0
-ITEM C $4.0
-ITEM D $8.5
Total Items :57.5
*******************DISCOUNTS**********************
-B2B – 10.0%
-Week Promotion – 5.0%
Total Porcentagem :15.0%
*******************OBSERVATIONS********************
Order Late
*************************************************
Total Discount: $15.0
Total Value: $48.875
Date to Pay: 14/06/2016
-> Send email
-> Send Sms
Code:
public class ObserverTest { @Test public void builderTest(){ BuilderInvoice invoice = new BuilderInvoice(); invoice.addActions( new EmailDAO() ) .addActions( new Printer() ) .addActions( new SenderEmail() ) .addActions( new SendSMS() ); invoice.addItem("ITEM A", 20.0) .addItem("ITEM B", 25.0) .addItem("ITEM C", 4.0) .addItem("ITEM D", 8.50) .addDiscount("B2B", 10) .addDiscount("Week Promotion", 5) .addObservations("Order Late") .close(); TestCase.assertFalse(true); } }
public interface ActionsAfterCloserInvoice { public void execute(Invoice invoice); }
public class EmailDAO implements ActionsAfterCloserInvoice { public void execute(Invoice invoice){ System.out.println("-> Persist in the database"); // Here the implementation to persist in the database } }
public class SenderEmail implements ActionsAfterCloserInvoice { public void execute(Invoice invoice){ // Here the implementation to send email System.out.println("-> Send email"); } }
public class SendSMS implements ActionsAfterCloserInvoice { public void execute(Invoice invoice){ // Here the implementation to send a sms System.out.println("-> Send Sms"); } }
public class Printer implements ActionsAfterCloserInvoice { public void execute(Invoice invoice){ System.out.println("*******************INVOICE**********************"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); System.out.println("Invoice Date Emission :" + invoice.getFinishDate().format(formatter) ); System.out.println("Invoice Date :" + invoice.getDate().format(formatter) ); System.out.println("*******************ITEMS**********************"); invoice.getOrder().getItemList().stream().forEach(i->{ System.out.println("-" + i.getName() + " $" + i.getValue()); }); System.out.println("Total Items :" + invoice.getOrder().getTotalValue() ); System.out.println("*******************DISCOUNTS**********************"); invoice.getDiscountList().stream().forEach(i->{ System.out.println("-" + i.getDescription() + " - " + i.getPorcent() + "%"); }); System.out.println("Total Porcentagem :" + invoice.getTotalDisountPorcentage() + "%" ); System.out.println("*******************OBSERVATIONS********************"); System.out.println( invoice.getOrder().getObservations()); System.out.println("*************************************************"); System.out.println("Total Discount: $"+ invoice.getTotalDiscount()); System.out.println("Total Value: $"+ invoice.getTotalInvoice()); System.out.println("Date to Pay: "+ invoice.getDatePayment().format(formatter)); } }
public class BuilderInvoice { private Invoice invoice ; private List<ActionsAfterCloserInvoice> actionsAfterCloserInvoices ; public BuilderInvoice() { actionsAfterCloserInvoices = new ArrayList<>(); invoice = new Invoice(); invoice.setDate(LocalDate.now()); invoice.setOrder(new Order()); invoice.getOrder().setDate(invoice.getDate()); } public BuilderInvoice addItem(String name, double value) { invoice.getOrder().getItemList().add(new Item(name,value)); return this; } public BuilderInvoice addDiscount(String desc, double por) { invoice.getDiscountList().add(new Discount(desc,por)); return this; } public BuilderInvoice changeDateOrder(LocalDate date) { invoice.getOrder().setDate(date); return this; } public BuilderInvoice addObservations(String obs) { invoice.getOrder().setObservations(obs); return this; } public BuilderInvoice close(){ double valueDiscount = (invoice.getOrder().getTotalValue() * invoice.getTotalDisountPorcentage())/100; double totalValue = invoice.getOrder().getTotalValue() - valueDiscount; invoice.setTotalInvoice(totalValue); invoice.setFinishDate(LocalDate.now()); invoice.setDatePayment(invoice.getFinishDate().plus(5, ChronoUnit.DAYS)); actions(); return this; } // Here we call the actions --> observers private void actions(){ actionsAfterCloserInvoices.stream().forEach(a-> a.execute(this.invoice)); } public BuilderInvoice addActions(ActionsAfterCloserInvoice actionsAfterCloserInvoice){ this.actionsAfterCloserInvoices.add(actionsAfterCloserInvoice); return this; } }
You also can check all implementations above in :
Great post! It’s very useful, Thanks
LikeLike