Java Strategy Pattern example
Strategy Design Pattern
The Strategy Design Pattern provides the ability of selecting a specific algorithm from a family of similar algorithms at runtime in order to complete a given task.
The concrete algorithm implementation itself is completely decoupled from the business component (or the context) that will actually make use of it. The client will select a concrete algorithm implementation and provide it to the business component at runtime.
This decoupling makes it easy for the client to change between algorithm implementations without the need for business code modification.
The Strategy pattern is achieved by the good practice of programming against interfaces: The business component holds a reference to an interface of an algorithm that processes a specific task. When the application is executing, the client will select a concrete algorithm implementation and pass it to the business component, which in turn will be able to process the task without knowing the details about the algorithm implementation chosen by the client.
In this article we will consider an illustrative product order placed in an online store. The order will know that there will be a payment processor entity that will be used to charge the consumer's credit card, but it will not know the specific details of the payment processor. It will only know that, at some point, it will be provided with a payment processor implementation that will be used to charge the consumer's credit card.
The Payment Processor
We will need an interface for our strategy (the payment processor):
package com.byteslounge.payment; public interface PaymentProcessor { void execute(int amount); }
And a couple of strategy implementations: Visa and Mastercard payment processors.
package com.byteslounge.payment; public class VisaPaymentProcessor implements PaymentProcessor { @Override public void execute(int amount) { System.out.println("Executing Visa payment: Charging $" + amount); } }
package com.byteslounge.payment; public class MastercardPaymentProcessor implements PaymentProcessor { @Override public void execute(int amount) { System.out.println("Executing Mastercard payment: Charging $" + amount); } }
The Context
We will now define the context (or the business component) that will actually make use of the strategy:
package com.byteslounge.order; import com.byteslounge.payment.PaymentProcessor; public class Order { private final PaymentProcessor paymentProcessor; private final int amount; public Order(int amount, PaymentProcessor paymentProcessor) { this.amount = amount; this.paymentProcessor = paymentProcessor; } public void process() { paymentProcessor.execute(amount); } }
We have just defined a class that will represent an illustrative order placed in an online store. The order consists of an amount that will be charged to the consumer and a payment processor. Note that the reference to the payment processor is done by using an interface.
This means that we may provide the order with any concrete payment processor implementation without the order knowing specific details about the processor implementation: they are decoupled.
Testing
Now we may write a simple test class:
package com.byteslounge; import com.byteslounge.order.Order; import com.byteslounge.payment.VisaPaymentProcessor; public class Main { public static void main(String[] args) { Order order = new Order(15, new VisaPaymentProcessor()); order.process(); } }
We are simply building an order which total amount is $15 and the payment processor will be the Visa processor. When we run the test class the following output will be generated:
Creational design patterns
As a final note we may still correlate the Strategy pattern with the creational patterns (ex: the Factory pattern). As we have just seen, the business component is decoupled from the concrete strategy implementation, but the client still needs to know about the concrete implementations and provide one to the business component.
We may also decouple the client from the concrete strategy implementations by using a creational pattern in order to produce the strategy implementations. For example, we may use the Factory pattern and create a factory that produces strategy implementations, and then let the client ask the factory for concrete strategies. The client itself would only hold a reference to the strategy interface so it becomes also decoupled from the concrete strategy implementations.
You may find more information in Java Factory Pattern example and Java Abstract Factory Pattern example.