SRP and OCP explained through a refactor, not a definition: turning a 'PaymentProcessor' god-class into something that can grow without being edited.
Published September 22, 2026
The first two of the five SOLID principles, and the two an interviewer is most likely to make you apply live rather than just define.
The common misreading is "a class should do one thing" — closer, but the precise version (Robert Martin's own phrasing) is a class should have only one reason to change. "One thing" is vague; "one reason to change" is testable: ask who would ask you to modify this class, and why. If the answer branches ("the finance team wants X, but also the logging format changed, but also validation rules changed"), it violates SRP even if every method individually looks reasonable.
class PaymentProcessor {
void validate(Order order) { /* business rule checks */ }
void charge(Order order) { /* calls payment gateway */ }
void logTransaction(Order order) { /* writes to a log file */ }
void sendConfirmationEmail(Order order) { /* SMTP call */ }
}
Four reasons this class changes: a validation rule changes, the payment gateway's API changes, the logging format changes, or the email template changes. Four unrelated teams can each have a reason to modify this one file — that's the smell, not any single method being "too long."
class OrderValidator { void validate(Order order) { ... } }
class PaymentGateway { void charge(Order order) { ... } }
class TransactionLogger { void log(Order order) { ... } }
class EmailNotifier { void sendConfirmation(Order order) { ... } }
class PaymentProcessor {
private final OrderValidator validator;
private final PaymentGateway gateway;
private final TransactionLogger logger;
private final EmailNotifier notifier;
// constructor takes all four (dependency injection)
void process(Order order) {
validator.validate(order);
gateway.charge(order);
logger.log(order);
notifier.sendConfirmation(order);
}
}
PaymentProcessor is now an orchestrator with one reason to change (the order of steps, or which steps run) — each concern lives in its own class with its own single reason to change. This also, as a side effect, makes each piece independently unit-testable — you can test OrderValidator without mocking a payment gateway.
A class should be extendable to support new behavior without editing its existing, already-tested code. The canonical fix is the Strategy pattern.
class PaymentGateway {
void charge(Order order, String method) {
if (method.equals("CREDIT_CARD")) { /* ... */ }
else if (method.equals("PAYPAL")) { /* ... */ }
else if (method.equals("CRYPTO")) { /* ... */ } // every new method = edit this class
}
}
Every new payment method means opening this class, adding a branch, and re-testing all existing branches for regression risk — the opposite of "closed for modification."
interface PaymentStrategy { void charge(Order order); }
class CreditCardStrategy implements PaymentStrategy { public void charge(Order order) { ... } }
class PayPalStrategy implements PaymentStrategy { public void charge(Order order) { ... } }
class PaymentGateway {
void charge(Order order, PaymentStrategy strategy) {
strategy.charge(order); // never needs to change for a new payment method
}
}
Adding crypto support now means writing a new CryptoStrategy class — zero changes to PaymentGateway or any existing strategy, zero regression risk to code that was already tested and shipped.
They're not independent — the SRP refactor (splitting concerns into their own classes) is often the precondition that makes an OCP-compliant extension point possible in the first place. You can't cleanly plug in a new PaymentStrategy if payment logic is still tangled inside a god-class doing four other things.
Q: Doesn't splitting one class into five just move complexity around instead of removing it? A: It relocates complexity from within one class (high internal coupling, many reasons to change) to between small classes with a single clear responsibility each — the total complexity of the system doesn't vanish, but each individual piece becomes easier to reason about, test, and change independently, which is the actual payoff.
Q: Can you take SRP too far? A: Yes — over-splitting into many trivial one-method classes adds indirection without a real independent "reason to change" for each, making the codebase harder to navigate for no benefit. SRP is a judgment call anchored to actual independent change drivers, not a mandate to minimize class size.
Q: How does OCP relate to the Open/Closed violation of a giant switch/if-else chain in general, not just payments? A: Any branch-per-type-of-thing structure (switch on an enum, if/else chain on a string type) is an OCP smell whenever new types get added over time — Strategy (or polymorphism generally, via an interface implemented per type) is the general-purpose fix, not something specific to payments.