Mastering the Strategy Pattern in Spring Boot: Clean, Extensible Code with Real Examples
This article walks through the concept and practical implementation of the Strategy design pattern in Spring Boot, showing how to define operation interfaces, create concrete strategy classes, use factories and Spring's IoC container for clean, maintainable, and extensible backend code.
1. Background Introduction
Strategy design pattern is widely known; using it properly in business development can make code more readable and improve extensibility.
2. Code Practice
First, define an
Operationinterface for calculation logic:
<code>public interface Operation {
/**
* Execute calculation
* @param a operand a
* @param b operand b
* @return result
*/
int execute(int a, int b);
}</code>Then implement four concrete strategies:
<code>public class AddOperation implements Operation {
@Override
public int execute(int a, int b) { return a + b; }
}
public class SubOperation implements Operation {
@Override
public int execute(int a, int b) { return a - b; }
}
public class MultiOperation implements Operation {
@Override
public int execute(int a, int b) { return a * b; }
}
public class DivOperation implements Operation {
@Override
public int execute(int a, int b) { return a / b; }
}</code>Create a factory to obtain the appropriate implementation based on a string key:
<code>public class OperatorFactory {
private static Map<String, Operation> operationMap = new HashMap<>();
static {
operationMap.put("add", new AddOperation());
operationMap.put("sub", new SubOperation());
operationMap.put("multi", new MultiOperation());
operationMap.put("div", new DivOperation());
// more operators
}
public static Optional<Operation> getOperation(String operator) {
return Optional.ofNullable(operationMap.get(operator));
}
}</code>Usage example:
<code>public class OperatorTestMain {
public static void main(String[] args) {
Operation targetOperation = OperatorFactory
.getOperation("add")
.orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
int result = targetOperation.execute(1, 2);
System.out.println("result:" + result);
}
}</code>3. SpringBoot Practice
3.1 Solution One (Recommended)
Define a
Commandinterface to abstract operations:
<code>public interface Command {
/** Command type identifier */
String operateType();
/** Execute operation */
Integer execute(int a, int b);
}</code>Implement four command classes:
<code>@Component
public class AddCommand implements Command {
@Override
public String operateType() { return "add"; }
@Override
public Integer execute(int a, int b) { return a + b; }
}
@Component
public class SubCommand implements Command {
@Override
public String operateType() { return "subtract"; }
@Override
public Integer execute(int a, int b) { return a - b; }
}
@Component
public class MultiCommand implements Command {
@Override
public String operateType() { return "multiply"; }
@Override
public Integer execute(int a, int b) { return a * b; }
}
@Component
public class DivCommand implements Command {
@Override
public String operateType() { return "divide"; }
@Override
public Integer execute(int a, int b) { return a / b; }
}</code>Create a service that collects all
Commandbeans from the Spring IoC container and performs calculations:
<code>@Component
public class CalculatorService implements ApplicationContextAware {
private Map<String, Command> commandMap = new ConcurrentHashMap<>();
public int calculate(String operateType, int a, int b) {
Command targetCommand = Optional.ofNullable(commandMap.get(operateType))
.orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
return targetCommand.execute(a, b);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Command> tempMap = applicationContext.getBeansOfType(Command.class);
tempMap.values().forEach(source -> commandMap.put(source.operateType(), source));
}
}</code>Test the service:
<code>@RunWith(SpringRunner.class)
@SpringBootTest
public class CalculatorServiceTest {
@Autowired
private CalculatorService calculatorService;
@Test
public void test() {
int result = calculatorService.calculate("add", 1, 2);
System.out.println("result:" + result);
}
}</code>3.2 Solution Two (Alternative)
Leverage Spring's automatic injection of all
Commandimplementations into a
Mapwithout writing a dedicated service.
<code>@Component
public class CommandFactory {
/** Spring injects all Command beans; key is bean name, value is the implementation */
@Autowired
private Map<String, Command> commandMap;
public int calculate(String operateType, int a, int b) {
Command targetCommand = Optional.ofNullable(commandMap.get(operateType))
.orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
return targetCommand.execute(a, b);
}
}</code>Use the factory directly in tests:
<code>@RunWith(SpringRunner.class)
@SpringBootTest
public class CalculatorServiceTest {
@Autowired
private CommandFactory commandFactory;
@Test
public void test() {
int result = commandFactory.calculate("addCommand", 1, 2);
System.out.println("result:" + result);
}
}</code>4. Conclusion
The article demonstrates how to introduce the Strategy pattern into a Spring Boot project, showing two practical approaches. Both improve code readability and extensibility, with the second approach taking advantage of Spring's IoC container to automatically wire strategy implementations.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.