Following SOLID design principles
Principle · Chapter 6
Overview
Section titled “Overview”SOLID is an acronym for five design principles that produce code that is more understandable, maintainable, reusable, testable, and flexible:
- S — Single Responsibility Principle (SRP)
- O — Open/Closed Principle (OCP)
- L — Liskov Substitution Principle (LSP)
- I — Interface Segregation Principle (ISP)
- D — Dependency Inversion Principle (DIP)
Treat them as guidelines, not absolutes — use judgment on when and how far to apply each.
Single Responsibility Principle (SRP)
Section titled “Single Responsibility Principle (SRP)”- Each class should have one responsibility — one reason to change. Group things that change for the same reason; separate things that change for different reasons.
- A class with multiple responsibilities is used in more places, so a change risks introducing defects across responsibilities and impacts more classes.
- SRP is how you build an orthogonal system at the class level; it relates to SoC, and DRY helps you achieve it.
- Not the same as “one public method per class” — a single responsibility may need several methods.
- Example: an
EmailServicethat also writes to a log file has two reasons to change. Fix by abstracting logging behind anILoggerinterface injected via the constructor, leavingEmailServiceresponsible only for sending email.
Open/Closed Principle (OCP)
Section titled “Open/Closed Principle (OCP)”- Software components should be open for extension but closed for modification — add new behavior via new code without editing existing, working code.
- Bertrand Meyer originally framed this via implementation inheritance; Robert C. Martin (“Uncle Bob”) reframed it around abstraction and interfaces — swap implementations without changing the code that depends on the interface.
- Example: a
Canvas.DrawShapewithif (shape is Rectangle) … if (shape is Circle) …must be modified for every new shape (violates OCP). Refactor to anIShapeinterface with aDraw()method; each shape implements its ownDraw(), andCanvasnever changes when a new shape is added.
Liskov Substitution Principle (LSP)
Section titled “Liskov Substitution Principle (LSP)”- Subtypes must be substitutable for their base types without altering correctness — a subclass can stand in for its base class with no surprises.
- Subtypes must not change the expected behavior of the base class; violations compile but cause unexpected behavior or runtime errors and confusing code.
- Classic counterexample:
Squareinheriting fromRectangle. Overriding width/height so they stay equal breaks callers that set them independently — a rectangle expected to compute area 6 (3×2) returns 4.Squareis not truly substitutable forRectangle; such violations cause hard-to-find defects in complex hierarchies.
Interface Segregation Principle (ISP)
Section titled “Interface Segregation Principle (ISP)”- Clients should not be forced to depend on members they don’t use. Prefer small, cohesive interfaces; split large (“fat”) interfaces into focused ones.
- Code smell: implementing an interface forces you to mark methods/properties as
NotSupported/not implemented — a sign the interface should be segregated. Fat interfaces increase coupling and hurt maintainability. - Example: a
Movieforced to implementIProduct.AuthorId(and needing an extraRunningTime) signals a bad interface. Refactor: baseIProduct(shared members) withIBook : IProduct(addsAuthorId) andIMovie : IProduct(addsRunningTime).
Dependency Inversion Principle (DIP)
Section titled “Dependency Inversion Principle (DIP)”- “High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.” (Robert & Micah Martin.)
- Direct dependency graphs (A → B → C, each newing up its concrete dependency) are tightly coupled, hard to maintain, and not unit-testable — you can’t inject mocks for concrete types.
- Invert it: high-level classes depend on interfaces; implementations depend on those interfaces. Enables loose coupling and true unit tests. Improves testability.
Inversion of Control (IoC)
Section titled “Inversion of Control (IoC)”- A design principle where reusable code (e.g. a framework) calls into your system, inverting the traditional flow where your system calls into a library.
- Broader than dependencies, but strongly associated with them — which is why DI containers were historically called IoC containers.
Dependency Injection (DI)
Section titled “Dependency Injection (DI)”A technique that supplies (injects) a class’s dependencies to achieve dependency inversion.
Benefits:
- Removes hardcoded dependencies; implementation can be chosen at compile-time or runtime (late binding) as long as you program to an interface.
- Loose coupling → easier to maintain, extend, and test.
- Testability: depend on abstractions, so dependencies can be mocked in unit tests.
- Parallel development: teams agree on shared interfaces and build implementations independently.
DI patterns:
| Pattern | How dependencies arrive | Notes |
|---|---|---|
| Constructor injection | Passed through the constructor | Preferred — dependencies are explicit; object can’t exist without them. Use guard clauses + readonly fields |
| Property injection | Set via a public property | Implicit; provide a default via lazy initialization; can restrict to set-once |
| Method injection | Passed through a method (e.g. Initialize) | Similar to property injection |
| Service Locator | A locator object resolves instances on demand | Considered an anti-pattern by many — it hides a class’s dependencies |
- DI containers (a.k.a. IoC containers) are frameworks that create and inject dependencies automatically. Not required to use DI, but they remove the manual “grunt work” once dependencies go several levels deep. Rolling your own automation effectively reinvents a container.
Related
Section titled “Related”- Applying SOLID underpins the practices for helping your team succeed — DI and loose coupling are what make unit testing with mocks possible.
Citations
Section titled “Citations”- Software Architect’s Handbook (Packt, 2018), Ch.6 “Following SOLID design principles”, pp. 437-460.