The Strategy is a design pattern that allows the software to chose one from a family of algorithms during the runtime. Each algorithm is implemented in its own class, which makes their clients interchangeable. Using the Strategy design pattern, a class can execute the same method in different ways, with different implementations. It is one of the patterns in the book Design Patterns by Gamma et al.
For example, consider a worker that needs to commute. He can use different strategies, like go by car, motorcycle, bicycle, or metro. The class worker
has a field transport
. And the transport
has the method commute
. The classes with the algorithms are Car
, Motorcycle
, Bicycle
, and Metro
. They need to implement the Transport
interface.
The call to the method worker.transport.commute
can use the different algorithms, I mean, different strategies. Note that the context is not responsible for choosing the suitable algorithm. This is done by the client that selects one strategy. The context just executes the method from one strategy; hence, context is independent of the implementations.
In this article, we are going to implement the Strategy Design Pattern with a Java enum. Enums have more features than most programmers know.
There is also some criticism on this approach once it violates the Open-Closed principle a bit. With enums is not possible to add new implementations at runtime. You should update the code. Let’s see the code.
The Validator Strategy
The first example is a file format validator for XML, JSON, YAML, or CSV content. In a traditional Strategy implementation, you need multiple classes, one for each algorithm. With an enum, you can implement your business rule in each constant.
public enum ValidatorStrategy {
XML {
@Override
void doValidation(String content) {
// The validation code goes here
System.out.println("This is a XML content");
}
},
JSON {
@Override
void doValidation(String content) {
// The validation code goes here
System.out.println("This is a JSON content");
}
},
YAML {
@Override
void doValidation(String content) {
// The validation code goes here
System.out.println("This is a YAML content");
}
},
CSV {
@Override
void doValidation(String content) {
// The validation code goes here
System.out.println("This is a CSV content");
}
};
abstract void doValidation(String content);
}
The context class receives the strategy from the client and executes the algorithm in the selected strategy.
public class ValidatorContext {
private ValidatorStrategy strategy;
public ValidatorContext(ValidatorStrategy strategy) {
this.strategy = strategy;
}
public void setValidator(ValidatorStrategy validator) {
this.strategy = validator;
}
public void runValidation(String content) {
strategy.doValidation(content);
}
}
The last component is the client that passes the validation strategy to the context. See that you always call the method validator.runValidation
, after setting the strategy.
ValidatorContext validator = new ValidatorContext(ValidatorStrategy.XML);
validator.runValidation("XML content");
validator.setValidator(ValidatorStrategy.YAML);
validator.runValidation("YAML content");
validator.setValidator(ValidatorStrategy.JSON);
validator.runValidation("JSON content");
The Date Formatter Strategy
This is another example of Strategy Design Pattern. Now, we have a more complex situation, where the context class will pass the implementation to the strategy class. This way, it is possible to use a Dependency Injection engine, like Spring.
public enum DateFormatterStrategy {
BRAZIL {
@Override
public String doFormat(DateFormatterImpl formatter, Date date) {
return formatter.formatBrazil(date);
}
},
BELGIUM {
@Override
public String doFormat(DateFormatterImpl formatter, Date date) {
return formatter.formatBelgium(date);
}
},
US {
@Override
public String doFormat(DateFormatterImpl formatter, Date date) {
return formatter.formatUS(date);
}
};
public abstract String doFormat(DateFormatterImpl formatter, Date date);
}
The implementation class is shown below. It contains the business rules to format the date for each country.
public class DateFormatterImpl {
private SimpleDateFormat sdfBrasil = new SimpleDateFormat("dd/MM/yyyy");
private SimpleDateFormat sdfBelgium = new SimpleDateFormat("dd.MM.yyyy");
private SimpleDateFormat sdfUS = new SimpleDateFormat("MM/dd/yyyy");
public String formatBrazil(Date date) {
return this.sdfBrasil.format(date);
}
public String formatBelgium(Date date) {
return this.sdfBelgium.format(date);
}
public String formatUS(Date date) {
return this.sdfUS.format(date);
}
}
The context class is quite the same as seen before. I mean, it does not have any noticeable difference.
public class DateFormatterContext {
private DateFormatterStrategy strategy;
private DateFormatterImpl formatter = new DateFormatterImpl();
public DateFormatterContext(DateFormatterStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(DateFormatterStrategy strategy) {
this.strategy = strategy;
}
public String format(Date date) {
return this.strategy.doFormat(formatter, date);
}
}
And the last client class is quite similar to the previous one. After setting the strategy, you execute the method formatter.format
, and then, the selected strategy will perform the correct algorithm.
Date date = new Date();
DateFormatterContext formatter = new DateFormatterContext(DateFormatterStrategy.BELGIUM);
System.out.println("Belgium: " + formatter.format(date));
formatter.setStrategy(DateFormatterStrategy.BRAZIL);
System.out.println("Brazil: " + formatter.format(date));
formatter.setStrategy(DateFormatterStrategy.US);
System.out.println("US: " + formatter.format(date));
Conclusion
The Java Enum is a convenient special type with advanced features that can help the programmer in different needs. In this case, we implemented the Strategy Design Pattern with them. This approach has pros, like simplicity and fewer classes. But also has cons, and the most notable is that it breaks a little bit the Open-Closed principle.
References
https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html