Use SOLID principles to reason about a small notification feature
SkillNexis · 2026-07-10 · Updated 2026-10-21
SOLID is a set of object-oriented design ideas, not a requirement to create five layers for every function. Consider a learning app that records an assignment and sends a confirmation. The design question is how to change delivery behaviour without accidentally changing assignment validation.
Single responsibility: separate reasons for change
A function that checks a submission, saves it and formats an email has several concerns. Keep validation independent of the delivery message. Then a wording change does not require touching the rules that decide whether a submission is valid. A small pure validation function can be enough; a separate class is not always necessary.
Open/closed: extend a real variation
If email and SMS delivery are both required, pass a delivery function to the confirmation workflow. The workflow can call deliver(message) without a growing list of channel-specific branches. If only email exists, keep the implementation straightforward until a second behaviour is needed.
Liskov substitution: preserve the contract
Suppose every delivery function resolves on success and rejects on failure. An SMS replacement that silently returns false on failure breaks the caller’s expectations. Either preserve the same contract or adapt the replacement explicitly. Compatible method names alone are not enough.
Interface segregation: request only what is needed
The confirmation workflow needs a way to send a message, not an entire account-management client. Pass the narrow dependency. Tests can then provide a small fake sender without implementing unrelated methods such as changing passwords or deleting accounts.
Dependency inversion: isolate external effects
Keep the decision to send a confirmation separate from the provider’s HTTP details. A test sender can collect messages in an array. Assert that a valid submission sends one message and an invalid submission sends none. In production, the provider adapter implements the same contract and handles its own request failures.
Try it and evaluate the cost
Write the validation rule first. Inject a sender that records calls. Change only the message text and rerun both tests. Then ask whether each new abstraction made a real change easier. More files are not proof of a better design. Practise boundary testing in the validation and release lesson, and response handling in the async HTTP lesson.