A Strategy encapsulates one interchangeable algorithm behind an interface so a caller can
pick the algorithm at runtime instead of hardcoding it. The tell-tale smell it cures is a
switch that grows a new arm every time a requirement lands.
Here is the version an interviewer will push back on:
class Checkout {
// This switch swells with every new payment method.
void pay(String method, int amount) {
switch (method) {
case "card": /* charge card */ break;
case "upi": /* charge upi */ break;
case "wallet": /* charge wallet */ break;
default: throw new IllegalArgumentException(method);
}
}
}
Every new method means editing Checkout, re-reading the whole switch, and risking the
other arms. Extract the varying step into a PaymentStrategy and inject it:
interface PaymentStrategy {
void pay(int amount);
}
class CardPayment implements PaymentStrategy {
public void pay(int amount) { /* charge card */ }
}
class UpiPayment implements PaymentStrategy {
public void pay(int amount) { /* charge upi */ }
}
class Checkout {
private PaymentStrategy strategy;
Checkout(PaymentStrategy strategy) { this.strategy = strategy; }
void setStrategy(PaymentStrategy strategy) { this.strategy = strategy; }
void pay(int amount) { strategy.pay(amount); }
}
Now adding a wallet is one new class plus one injection point; Checkout never changes.
That is the open-closed payoff: open to new behavior, closed to edits of tested code. The
same shape covers a SortStrategy, a pricing rule, or a compression codec — anywhere the
"what to do" varies while the "when to do it" stays fixed.