How can we apply the Open/Closed Principle using the Strategy Pattern?*
The Open/Closed Principle (OCP) states that software entities should be open for extension but closed for modification.
In practice, this means that we should be able to add new behaviors without constantly changing code that already works.
Imagine a system that supports different payment methods, such as PIX, credit cards, and bank slips.
When all payment rules are concentrated in a single method using multiple if, else, or switch statements, every new payment method requires modifying the existing service.
The Strategy Pattern helps solve this problem by defining a common abstraction and separating each payment behavior into its own implementation.
This allows us to introduce a new payment method by creating a new strategy without changing the service responsible for selecting and executing it.
In this relationship:
Open/Closed is the principle we want to follow.
Strategy is a well-known design pattern that helps us apply it through abstraction, composition, and polymorphism.
Here is a complete example using Java and Spring Boot:
public interface PaymentStrategy {
String getType();
void pay(BigDecimal amount);
}
Each payment method implements the same interface:
@Component
public class PixPaymentStrategy implements PaymentStrategy {
@Override
public String getType() {
return "PIX";
}
@Override
public void pay(BigDecimal amount) {
System.out.println(
"Processing PIX payment: " + amount
);
}
}
@Component
public class CreditCardPaymentStrategy
implements PaymentStrategy {
@Override
public String getType() {
return "CREDIT_CARD";
}
@Override
public void pay(BigDecimal amount) {
System.out.println(
"Processing credit card payment: " + amount
);
}
}
@Component
public class BoletoPaymentStrategy
implements PaymentStrategy {
@Override
public String getType() {
return "BOLETO";
}
@Override
public void pay(BigDecimal amount) {
System.out.println(
"Generating bank slip: " + amount
);
}
}
Spring injects all implementations of PaymentStrategy into the service.
The service then creates a map in which the payment type is the key and the corresponding strategy is the value:
@Service
public class PaymentService {
private final Map<String, PaymentStrategy> strategies;
public PaymentService(
List<PaymentStrategy> strategyList
) {
this.strategies = strategyList.stream()
.collect(
Collectors.toMap(
strategy -> strategy
.getType()
.toUpperCase(),
Function.identity()
)
);
}
public void pay(
String paymentType,
BigDecimal amount
) {
PaymentStrategy strategy = strategies.get(
paymentType.toUpperCase()
);
if (strategy == null) {
throw new UnsupportedPaymentMethodException(
"Unsupported payment method: "
+ paymentType
);
}
strategy.pay(amount);
}
}
A custom exception can be used when the requested strategy does not exist:
public class UnsupportedPaymentMethodException
extends RuntimeException {
public UnsupportedPaymentMethodException(
String message
) {
super(message);
}
}
The request object receives the payment type and amount:
public record PaymentRequest(
String paymentType,
BigDecimal amount
) {
}
Finally, the controller delegates the request to the payment service:
@RestController
@RequestMapping("/payments")
public class PaymentController {
private final PaymentService paymentService;
public PaymentController(
PaymentService paymentService
) {
this.paymentService = paymentService;
}
@PostMapping
public ResponseEntity<Void> pay(
@RequestBody PaymentRequest request
) {
paymentService.pay(
request.paymentType(),
request.amount()
);
return ResponseEntity.noContent().build();
}
}
Now imagine that the application needs to support PayPal.
We only need to add a new implementation:
@Component
public class PaypalPaymentStrategy
implements PaymentStrategy {
@Override
public String getType() {
return "PAYPAL";
}
@Override
public void pay(BigDecimal amount) {
System.out.println(
"Processing PayPal payment: " + amount
);
}
}
The PaymentService does not need to be changed.
Spring automatically discovers the new component and injects it into the list of available strategies.
The system remains:
Open for extension: new payment strategies can be added.
Closed for modification: the service responsible for processing payments remains unchanged.
The Open/Closed Principle defines the design goal.
The Strategy Pattern provides a practical way to achieve it.
Top comments (0)