SOLID Principles
Step 3 in the OO & Low-Level Design path · 23 concepts · 0 problems
📘 Learn SOLID Principles from zero
Imagine a busy restaurant kitchen. If one person took orders, cooked, washed dishes, and did payroll, every menu change or new health rule would disrupt that single overloaded person, and replacing them would collapse everything. Good kitchens split work into stations (grill, prep, dish) with clear handoffs, so changing one station rarely disturbs the others. This is Separation of Concerns: each station is highly cohesive (its tools all serve one job) and loosely coupled (stations talk through a simple ticket interface, not by reaching into each other's pans). SOLID — five principles collected by Robert C. Martin (the acronym coined by Michael Feathers) — applies the same idea to object-oriented code so change stays cheap and safe.
The five: Single Responsibility (one reason to change / one actor — the class-level form of "high cohesion, low coupling"), Open-Closed (open to extension, closed to modification), Liskov Substitution (a subtype must be usable anywhere its base type is, honoring the same contract: it may not strengthen preconditions or weaken postconditions), Interface Segregation (no client forced to depend on methods it doesn't use), Dependency Inversion (both layers depend on abstractions, not concretions).
Worked example. Suppose Invoice calculates totals and prints itself and saves to a database. A tax-law change (accounting), a UI redesign (design), and a database swap (ops) are three different actors all forcing edits to one file — risky and low-cohesion. Applying SRP, split into Invoice (totals), InvoicePrinter, and InvoiceRepository. Now apply DIP: Invoice talks to a Repository interface it owns, not MySQLRepository, so you can swap storage or inject a fake in tests — that's reduced coupling. The force across all five is the same: localize change — separate the things that vary independently and let them communicate through stable abstractions.
Key insight to remember: SOLID is not about more code; it is high cohesion plus loose coupling made concrete, so each axis of change is isolated and a future requirement touches one place, not many.
✨ Added by the guide to build intuition — not from the source course.
🎯 Guided practice
Easy — spot the violation. A class
UserServicehas methodsregister(user),sendWelcomeEmail(user), andrenderProfileHtml(user). Which SOLID principle is broken, and why?Reasoning: Ask "how many actors would request a change to this class?" Registration logic answers to the business/auth owner; the email body answers to marketing; the HTML answers to the front-end team. That's three independent actors, so three reasons to change — a Single Responsibility violation, and concretely a cohesion problem (unrelated jobs glued together). Fix: keep
UserService.register, extractEmailNotifierand aProfileView(or template). Each unit is now cohesive and changes for exactly one reason, and you can test registration without touching email/UI.Medium — design with Open-Closed + DIP. A checkout computes shipping cost with
if (type=="air") … else if (type=="ground") … else if (type=="drone") …. Each new carrier means editing this method. Redesign so adding a carrier requires no edits to existing code.Reasoning: The smell is a growing type-switch — the signal for Open-Closed. Step 1: define an abstraction,
interface ShippingStrategy { Money cost(Package p); }. Step 2: make each carrier its own class implementing it:AirShipping,GroundShipping,DroneShipping— adding a carrier is now a new class (extension), not a modification of checkout (closed for modification). Step 3: apply Dependency Inversion —Checkoutholds aShippingStrategyinjected via its constructor, so it depends on the interface it owns, never on a concrete carrier (loose coupling). Step 4 (guard with Liskov): every strategy must honor the same contract — accept any validPackagethe interface promises (no strengthened precondition), and always return a non-negativeMoneywithout throwing for valid input (no weakened postcondition) — soCheckoutcan substitute any of them blindly. (Note: a real dispatcher still needs one place to select the strategy from input, e.g. a factory or registry; OCP confines that to a single isolated map, keeping the cost logic itself closed.) Result: new carriers plug in; the tested checkout code stays untouched.
✨ Added by the guide — work these before the full problem set.
Lessons in this topic
- ○What are SOLID Design Principles
- ○Why Learn SOLID Principles
- ○Introduction to the Single Responsibility Principle
- ○Cohesion and its Relation to the Single Responsibility Principle SRP
- ○Coupling and its Relation to the Single Responsibility Principle SRP
- ○Separation of Concerns
- ○SRP vs Coupling Cohesion Separation of Concerns
- ○Wrap Up
- ○Introduction to Open Closed Principle
- ○Real World Analogies and Code Example
- ○Final Thoughts on the OpenClosed Principle and Its Relation to Other SOLID Principles
- ○Introduction to the Liskov Substitution Principle
- ○Break The Hierarchy for Adhering to LSP
- ○Using Composition to Follow Liskov Substitution Principle LSP
- ○Wrap Up (2)
- ○Introduction to the Interface Segregation Principle
- ○Restructuring the code to follow ISP
- ○Techniques to Identify ISP Violations
- ○Wrap Up (3)
- ○Introduction to Dependency Inversion Principle
- ○Refactoring Code to Follow DIP
- ○DIP in Practice Real-World Examples
- ○Wrap Up (4)