The Strategy Pattern is a behavioral design pattern that allows a class's behavior to be selected at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern is useful in scenarios where multiple algorithms or behaviors need to be applied dynamically.
Implementations of the Strategy Pattern in Java SE 17
- Using Functional Interfaces (Java 8 and later)
- Using Traditional Interfaces and Concrete Classes
- Using Enum-based Strategy
- Using Records (Java 14+)
- Using Sealed Classes (Java 17)
1. Functional Interface Strategy (Lambda Expressions)
Since Java 8, the Strategy Pattern can be implemented using functional interfaces and lambda expressions.
@FunctionalInterface
interface PaymentStrategy {
void pay(int amount);
}
class ShoppingCart {
private final PaymentStrategy paymentStrategy;
public ShoppingCart(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
public class StrategyLambdaExample {
public static void main(String[] args) {
PaymentStrategy creditCardPayment = amount -> System.out.println("Paid " + amount + " using Credit Card.");
PaymentStrategy paypalPayment = amount -> System.out.println("Paid " + amount + " using PayPal.");
ShoppingCart cart1 = new ShoppingCart(creditCardPayment);
cart1.checkout(100);
ShoppingCart cart2 = new ShoppingCart(paypalPayment);
cart2.checkout(200);
}
}
Advantage: Cleaner and more concise.
2. Traditional Interface-Based Strategy
This is the classic approach where different strategies are implemented as separate classes.
interface CompressionStrategy {
void compress(String file);
}
class ZipCompression implements CompressionStrategy {
public void compress(String file) {
System.out.println("Compressing " + file + " using ZIP.");
}
}
class RarCompression implements CompressionStrategy {
public void compress(String file) {
System.out.println("Compressing " + file + " using RAR.");
}
}
class CompressionContext {
private CompressionStrategy strategy;
public void setStrategy(CompressionStrategy strategy) {
this.strategy = strategy;
}
public void compressFile(String file) {
strategy.compress(file);
}
}
public class StrategyInterfaceExample {
public static void main(String[] args) {
CompressionContext context = new CompressionContext();
context.setStrategy(new ZipCompression());
context.compressFile("file1.txt");
context.setStrategy(new RarCompression());
context.compressFile("file2.txt");
}
}
Advantage: More flexible and easier to extend.
3. Enum-Based Strategy (Java 17)
Java enums can define strategy behavior.
enum SortingStrategy {
BUBBLE_SORT {
@Override
void sort(int[] numbers) {
System.out.println("Sorting using Bubble Sort.");
}
},
QUICK_SORT {
@Override
void sort(int[] numbers) {
System.out.println("Sorting using Quick Sort.");
}
};
abstract void sort(int[] numbers);
}
class Sorter {
private SortingStrategy strategy;
public Sorter(SortingStrategy strategy) {
this.strategy = strategy;
}
public void executeSort(int[] numbers) {
strategy.sort(numbers);
}
}
public class EnumStrategyExample {
public static void main(String[] args) {
Sorter sorter = new Sorter(SortingStrategy.BUBBLE_SORT);
sorter.executeSort(new int[]{5, 3, 8, 1});
sorter = new Sorter(SortingStrategy.QUICK_SORT);
sorter.executeSort(new int[]{5, 3, 8, 1});
}
}
Advantage: Provides a type-safe and maintainable strategy pattern.
4. Record-Based Strategy (Java 14+)
Records (introduced in Java 14) allow for concise data carriers but can also be used in the Strategy Pattern.
interface DiscountStrategy {
double applyDiscount(double price);
}
record NoDiscount() implements DiscountStrategy {
public double applyDiscount(double price) {
return price;
}
}
record SeasonalDiscount(double percentage) implements DiscountStrategy {
public double applyDiscount(double price) {
return price - (price * percentage / 100);
}
}
class BillingSystem {
private final DiscountStrategy strategy;
public BillingSystem(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double calculatePrice(double price) {
return strategy.applyDiscount(price);
}
}
public class RecordStrategyExample {
public static void main(String[] args) {
BillingSystem normalBill = new BillingSystem(new NoDiscount());
System.out.println("Final Price: " + normalBill.calculatePrice(100));
BillingSystem seasonalBill = new BillingSystem(new SeasonalDiscount(10));
System.out.println("Final Price: " + seasonalBill.calculatePrice(100));
}
}
Advantage: Uses immutable data structures, making it more predictable.
5. Sealed Class Strategy (Java 17)
Java 17 introduced "sealed classes", which restrict which classes can extend a base class.
sealed interface FileCompressionStrategy permits ZipCompression, RarCompression {
void compress(String file);
}
final class ZipCompression implements FileCompressionStrategy {
public void compress(String file) {
System.out.println("Compressing " + file + " using ZIP.");
}
}
final class RarCompression implements FileCompressionStrategy {
public void compress(String file) {
System.out.println("Compressing " + file + " using RAR.");
}
}
class CompressionClient {
private final FileCompressionStrategy strategy;
public CompressionClient(FileCompressionStrategy strategy) {
this.strategy = strategy;
}
public void compressFile(String file) {
strategy.compress(file);
}
}
public class SealedClassStrategyExample {
public static void main(String[] args) {
CompressionClient client = new CompressionClient(new ZipCompression());
client.compressFile("document.txt");
client = new CompressionClient(new RarCompression());
client.compressFile("image.png");
}
}
Advantage: Improves encapsulation while maintaining flexibility.
Conclusion: The Strategy Pattern in Java SE 17 can be implemented in multiple ways:
- Functional Interfaces (Lambdas) – Cleaner and more concise.
- Traditional Interfaces with Concrete Classes – More flexible and extensible.
- Enum-Based Strategy – Type-safe and easy to manage.
- Record-Based Strategy – More immutable and lightweight.
- Sealed Classes – Enforces controlled inheritance.
The following is an example of a file compression tool where a zip or rar file is created.
The Strategy interface is defined first:
package com.java.behavioral.strategy;
//Strategy Interface
public interface CompressionStrategy{
public void compressFiles(ArrayList<File> files);
}
Two implementations are provided,
- one for zip and
- one for rar
package com.java.behavioral.strategy;
public class ZipCompressionStrategy implements CompressionStrategy{
public void compressFiles(ArrayList<File> files){
//using ZIP approach
}
}
package com.java.behavioral.strategy;
public class RarCompressionStrategy
implements CompressionStrategy{
public void compressFiles(ArrayList<File> files){
//using RAR approach
}
}
The
context will provide a way for the client to compress the files. Assume there is a preferences setting in our application that sets which compression algorithm to use. We can change our strategy using the setCompressionStrategy method in the Context.
package com.java.behavioral.strategy;
public class CompressionContext{
private CompressionStrategy str;
//this can be set at runtime by the application preferences
public void setCompressionStrategy(CompressionStrategy strat){
this.str = strat;
}
//use the strategy
public void createArchive(ArrayList<File> files){
strategy.compressFiles(files);
}
}
All the client has to do now is pass through the files to the CompressionContext
package com.java.behavioral.strategy;
public class Client{
public static void main(String[] args){
CompressionContext ctx = new CompressionContext();
//we could assume context is already set by preferences
ctx.setCompressionStrategy(new ZipCompressionStrategy());
//get a list of files
...
ctx.createArchive(fileList);
}
}