Creating multiple methods inside only one class can bring many problems like inflexibility and difficulty maintaining code. I am sure all of us have already seen a class full of methods and different responsibilities, right?
Classes like these are very difficult to understand, take a lot of time to find out what is happening in all methods, and are difficult to test. Fortunately, there is a way to solve this problem – we can use the Command Pattern! The Command Pattern encapsulates its behavior in a separate class that is executed by the Invoker class.
Get the Design_Patterns_Saga_GitHub_Source_Code
A very common example of the Command Pattern is the Thread class that receives the Runnable interface with the command inside the run method.
public class ThreadCommandExample { public static void main(String[] args) { TaskCommand command = new TaskCommand(10, 12); Thread invoker = new Thread(command); invoker.start(); } static class TaskCommand implements Runnable { int num1; int num2; TaskCommand(int num1, int num2) { this.num1 = num1; this.num2 = num2; } @Override public void run() { //execute method System.out.println(num1 * num2); //receiver } } }
1 – Command interface: This is the base of the Pattern, without this generic interface nothing happens. No secrets here, just an interface that has the execute method, nothing more.
public interface Command { public void execute(); }
2 – Discount class: this class contains the information to be processed. It’s a simple POJO.
public class Discount { private boolean eligible; private boolean discountApplied; public Discount(boolean eligible) { this.eligible = eligible; } public void applyDiscount() { discountApplied = true; System.out.println("Discount applied!"); } // Getters and setters omitted }
3 – Commands: These actions must be executed inside the Invoker. The ApplyDiscountCommand has the business requirements in order to apply the discount. The ApplyAllDiscountsCommand is responsible for encapsulating the ApplyDiscountCommand in order to apply all the discounts once. So, this Command receives a Discount list and invokes the applyDiscount method.
public class ApplyDiscountCommand implements Command { private Discount discount; public ApplyDiscountCommand(Discount discount) { this.discount = discount; } @Override public void execute() { if (discount.isEligible()) { discount.applyDiscount(); } } } public class ApplyAllDiscountsCommand implements Command { private List<Discount> discounts; public ApplyAllDiscountsCommand(List<Discount> discounts) { this.discounts = discounts; } @Override public void execute() { discounts.forEach(e -> new ApplyDiscountCommand(e).execute()); } }
4 – CommandInvoker: As the name says, it’s going to invoke the Command. We are receiving the Command in the Constructor to be executed in the execute method.
public class CommandInvoker { private Command command; public CommandInvoker(Command command) { this.command = command; } public void execute() { this.command.execute(); } }
5 – Unit Tests: All are set to execute Unit Tests. To execute the Command, create the Discount list. Then use the CommandInvoker passing the Command in the Constructor. In the end, execute the Command!
Now, ensure the discounts were applied correctly!
public class CommandTest { @Test public void applyCommandTest() { List<Discount> discounts = mockDiscounts(); ApplyAllDiscountsCommand command = new ApplyAllDiscountsCommand(discounts); CommandInvoker invoker = new CommandInvoker(command); invoker.execute(); Assert.assertTrue(discounts.get(0).isDiscountApplied()); Assert.assertFalse(discounts.get(1).isDiscountApplied()); Assert.assertTrue(discounts.get(2).isDiscountApplied()); Assert.assertFalse(discounts.get(3).isDiscountApplied()); } private List<Discount> mockDiscounts() { return Arrays.asList(new Discount(true), new Discount(false), new Discount(true), new Discount(false)); } }
Summary of actions:
1 – Created the Command interface.
2 – Created the request object (POJO).
3 – Created the Commands implementing the Command interface.
4 – Implemented the execute method in each Command.
5 – Created the CommandInvoker class to execute the commands.
6 – Invoked the Command through the InvokerCommand class.
To practice the Command Pattern you can create another Discount Command, for example, GoalDiscountCommand which is responsible for applying a Discount depending on the price the client paid for the product, and create another test method to make sure it works! Try to use TDD (Test Driven Development). Start the development from the test. Remember, the only way to master the Design Patterns is by practicing them! So, I strongly recommend you clone the project and implement your new Discount Command!