A learning path ready to make your own.

SOLID

Abstract SOLID is a compact set of five object-oriented design principles (SRP, OCP, LSP, ISP, DIP) that promote maintainable, extensible, and testable software. Originating from earlier formal work (notably Liskov) and popularized by Robert C. Martin, SOLID emphasizes cohesion, low coupling, abstraction, and dependency management. This summary covers the principles, theoretical foundations, practical patterns, common trade-offs, enforcement, cross-paradigm applicability, and practical refactor recipes. The Five Principles (quick) Single Responsibility Principle (SRP) — A class/module should have one reason to change. Encourages high cohesion and separation of concerns; refactor by extracting distinct responsibilities into separate classes. Open/Closed Principle (OCP) — Entities should be open for extension but closed for modification. Use abstraction, polymorphism or composition (strategy pattern) to add behavior without changing tested code. Liskov Substitution Principle (LSP) — Subtypes must be substitutable for supertypes without breaking correctness; focus on behavioral contracts (pre/postconditions, invariants) rather than just signatures. Interface Segregation Principle (ISP) — Prefer many specific client-facing interfaces over one fat interface so implementers aren’t forced to support irrelevant operations. Dependency Inversion Principle (DIP) — High-level modules depend on abstractions, not concrete implementations. Apply constructor injection or DI containers to invert dependencies and improve testability. Theoretical Foundations Cohesion & Coupling: SOLID seeks high cohesion and low coupling via modularization, interfaces and dependency management. Behavioral Subtyping: LSP is grounded in design-by-contract: subtypes must not strengthen preconditions or weaken postconditions. Abstraction & Information Hiding: Encourages coding to interfaces and hiding details (Parnas-style modularization). Practical Applications & Supporting Patterns Unit testing & mockability improve when classes are small and dependencies are injectable. Dependency Injection (manual or via IoC containers like Spring, Guice, .NET Core DI) realizes DIP in production. Common patterns that help: Strategy, Factory, Adapter, Decorator, Repository, Command, Template Method. Prefer composition over inheritance to avoid LSP problems. Architecture patterns: Clean Architecture, Hexagonal/Ports & Adapters, Onion — these apply SRP and DIP to separate domain from infrastructure. Common Misapplications & Trade-offs Premature abstraction: Creating interfaces for everything (YAGNI) leads to complexity and interface proliferation. Over-fragmentation: Too many tiny classes/interfaces can reduce readability and slow navigation. Hidden dependencies: Service locator or excessive indirection obscures what modules need. Performance & pragmatics: Abstraction layers add small runtime and cognitive costs—sometimes pragmatic deviations are justified. Balance is required: apply SOLID judiciously against team familiarity, readability and concrete needs. Measuring & Enforcing SOLID Static analysis tools and linters (SonarQube, ReSharper, language-specific analyzers) detect size, complexity and some coupling issues. Useful metrics: Coupling Between Objects (CBO), Lack of Cohesion of Methods (LCOM), cyclomatic complexity, maintainability index. Code reviews and architectural reviews remain essential to catch behavioral (LSP) and design-intent problems tools cannot infer. SOLID Beyond OOP Functional Programming: SOLID goals map to small pure functions, composition/higher-order functions, parameter-passing for dependencies, and strong types for substitutability. Behavioral contracts remain relevant. Microservices & Distributed Systems: SRP → single-purpose services/bounded contexts; OCP → API versioning/backward compatibility; DIP → protocol/contract abstraction. Operational concerns (latency, observability, distributed transactions) introduce trade-offs. Current Trends & Future Directions SOLID remains core for maintainable OO design but is increasingly integrated with Clean Architecture and DDD. Tooling improvements (automated refactorings, dependency graphs, architecture-as-code) help enforce SOLID at scale. AI/code generation can aid with idiomatic patterns and tests but may also produce over-abstraction or boilerplate—human oversight is needed. Growing interest in formal behavioral specifications (types, design-by-contract, verification) to make LSP checks more rigorous. Practical Checklist & Refactoring Recipes SRP: Does the class have more than one reason to change? Consider Extract Class. OCP: Do you have long switch/if chains? Replace conditionals with polymorphism/strategy. LSP: Will derived types meet clients’ expectations? Replace inheritance with composition or extract interfaces when needed. ISP: Are implementers forced to provide unused methods? Split interfaces. DIP: Do high-level modules instantiate low-level concretes? Introduce abstractions and use constructor injection. Common refactor recipes: Extract Class, Extract Interface, Replace Conditional with Polymorphism, Introduce Adapter, Replace Inheritance with Composition. Conclusion SOLID offers practical guidance for designing maintainable, extensible, and testable systems. Its strength lies in promoting separation of concerns, stable abstractions, and behavioral correctness. However, SOLID is not dogma—apply it pragmatically, balancing abstraction with readability, performance and team context. Suggested References Robert C. Martin, Agile Software Development, Principles, Patterns, and Practices Barbara Liskov, Data Abstraction and Hierarchy (LSP) Erich Gamma et al., Design Patterns David Parnas, On the Criteria to Be Used in Decomposing Systems into Modules Martin Fowler, Refactoring If you want, I can provide a runnable, language-specific example project demonstrating SOLID end-to-end, generate a refactoring plan for a concrete class/module you provide, or create a checklist/static-analysis rule-set tailored to your language/framework—which would be most helpful?

Let the lesson walk with you.

Podcast

SOLID podcast

0:00-2:38

Follow the trail that experts already trust.

Resources

Turn quick sparks into lasting recall.

Flashcards

SOLID flashcards

15 cards

Question

Click to flip
Answer

Prove the idea before it slips away.

Quizzes

SOLID quiz

13 questions

Who coined the SOLID acronym?

Read deeper, connect wider, own the subject.

Deep Article

SOLID: A Deep Dive into Object-Oriented Design Principles

Abstract


SOLID is a set of five design principles that guide the creation of maintainable, extensible, and testable object-oriented software. Introduced and popularized by Robert C. Martin ("Uncle Bob") and grounded in earlier formal work (notably Barbara Liskov’s work on substitutability), SOLID encapsulates essential ideas about cohesion, coupling, abstraction, and dependency management. This article examines the history, theory, practical application, code examples, limitations, and future directions of SOLID. It also shows how SOLID interacts with modern architectures—microservices, domain-driven design (DDD), functional programming paradigms—and automated tooling.

Table of contents


  • History and Origins
  • Overview of the Principles
  • Single Responsibility Principle (SRP)
  • Open/Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)
  • Theoretical Foundations
  • Cohesion and Coupling
  • Behavioral Subtyping and Liskov’s Formalism
  • Abstraction and Information Hiding
  • Practical Applications and Design Patterns
  • Unit Testing and Testability
  • Dependency Injection and IoC Containers
  • Patterns that Support SOLID
  • Architecture-level Patterns: Clean Architecture, Hexagonal, Layered
  • Examples and Code
  • SRP examples (before / after)
  • OCP examples (open for extension)
  • LSP examples (behavioral substitutions)
  • ISP examples (interface segregation)
  • DIP examples (high-level modules and abstractions)
  • Common Misapplications, Anti-patterns, and Trade-offs
  • Measuring & Enforcing SOLID
  • SOLID Beyond OOP: Functional, Distributed, and Microservice Contexts
  • Current State, Trends, and Future Directions
  • Practical Checklist & Refactoring Recipes
  • Conclusion
  • Suggested References

History and Origins


  • SOLID acronym was coined by Michael Feathers but popularized by Robert C. Martin (Uncle Bob) in the early 2000s as a concise way to remember five core object-oriented design principles.
  • Origins tie into older, foundational ideas:
  • Single Responsibility echoes separation of concerns (E. W. Dijkstra and others).
  • Liskov Substitution Principle formalizes substitutability (Barbara Liskov, 1987).
  • Interface Segregation and Dependency Inversion were influenced by modularization practices and the push for greater decoupling in software systems.
  • Over time SOLID became a central teaching tool in software engineering, guiding both day-to-day code decisions and larger architecture choices.

Overview of the Principles


Single Responsibility Principle (SRP)

  • Definition: A class should have one, and only one, reason to change.
  • Intent: Maximize cohesion; minimize coupling by ensuring every module/actor has a single responsibility.
  • Rationale: When responsibilities are mixed, changes for one reason can introduce bugs affecting another. Smaller, focused units are easier to test and refactor.
  • Common code smell: God classes or classes that do persistence, business logic, and formatting at once.

Example (Java-like pseudocode — violation): ``java class InvoiceManager { void calculateTotals(Invoice invoice) { ... } // business logic void saveInvoice(Invoice invoice) { ... } // persistence void printInvoice(Invoice invoice) { ... } // UI/formatting } ``

Refactor (SRP applied): ``java class InvoiceCalculator { ... } class InvoiceRepository { ... } class InvoicePrinter { ... } ``

Open/Closed Principle (OCP)

  • Definition: Software entities (classes, modules, functions) should be open for extension, but closed for modification.
  • Intent: Allow system behavior to be extended without altering existing tested code, reducing regression risk.
  • Rationale: Use abstraction, polymorphism, and composition to add features without touching stable code.
  • Common code smell: Large conditional statements (if/else, switch) that require editing for new behaviors.

Example (violation): ``java class DiscountService { double applyDiscountOrder(Order o) { if (o.type == OrderType.REGULAR) return o.total 0.95; if (o.type == OrderType.PREMIUM) return o.total 0.90; // adding new types requires editing this class } } ``

Refactor (OCP applied using strategy pattern): ```java interface DiscountPolicy { double apply(Order o); } class RegularDiscount implements DiscountPolicy { ... } class PremiumDiscount implements DiscountPolicy { ... }

class DiscountService { DiscountPolicy policy; double applyDiscount(Order o) { return policy.apply(o); } } ```

Liskov Substitution Principle (LSP)

  • Definition: Objects of a superclass should be replaceable with objects of a subclass without altering the desirable properties of the program (correctness, task performed).
  • Intent: Subtypes must honor the contract of supertypes—behavioral compatibility matters, not just method signatures.
  • Rationale: Inheritance should preserve behavior; violating it causes subtle bugs when substituting instances.
  • Common code smell: Subclasses overriding behavior in a way that changes expectations (e.g., throwing unexpected exceptions or breaking invariants).

Classic example (Rectangle-Square problem): ``java class Rectangle { int width, height; void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } } class Square extends Rectangle { void setWidth(int w) { width = height = w; } // breaks LSP void setHeight(int h) { width = height = h; } } `` Using a Square where Rectangle is expected may violate invariants and client code expectations.

Interface Segregation Principle (ISP)

  • Definition: Clients should not be forced to depend on interfaces they do not use. Provide many client-specific interfaces instead of one general-purpose interface.
  • Intent: Reduce coupling between clients and large, fat interfaces.
  • Rationale: Smaller interfaces lead to clearer contracts and easier mocking/testing.
  • Common code smell: Large interfaces that require classes to implement methods not relevant to them.

Example (violation): ```java interface MultiDevicePrinter { void print(Document d); void scan(Document d); void fax(Document d); }

class SimplePrinter implements MultiDevicePrinter { void print(Document d) { ... } void scan(Document d) { throw new UnsupportedOperationException(); } void fax(Document d) { throw new UnsupportedOperationException(); } } ```

Refactor (ISP applied): ```java interface Printer { void print(Document d); } interface Scanner { void scan(Document d); } interface Fax { void fax(Document d); }

class SimplePrinter implements Printer { ... } ```

Dependency Inversion Principle (DIP)

  • Definition: 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.
  • Intent: Invert conventional dependency flow so design depends on stable interfaces, not volatile implementations.
  • Rationale: Makes high-level policy independent of low-level implementation details; facilitates swapping implementations and testing.
  • Common code smell: High-level class instantiates low-level concrete classes directly.

Example (violation): ``java class UserService { private UserRepository repo = new SqlUserRepository(); // tight coupling User getUser(String id) { return repo.find(id); } } ``

Refactor (DIP via constructor injection): ```java interface UserRepository { User find(String id); }

class UserService { private final UserRepository repo; UserService(UserRepository repo) { this.repo = repo; } User getUser(String id) { return repo.find(id); } } ```

Theoretical Foundations


Cohesion and Coupling

  • Cohesion: Degree to which elements of a module belong together. High cohesion is desired.
  • Coupling: Degree of interdependence between modules. Low coupling is desired.
  • SOLID is fundamentally about increasing cohesion and reducing coupling via modularization, interfaces, and dependency handling.

Behavioral Subtyping (Liskov)

  • LSP is not merely about method signatures—it is about behavior and contracts (preconditions, postconditions, invariants).
  • Formalized through concepts like Design by Contract: a subtype must not strengthen preconditions or weaken postconditions.

Abstraction & Information Hiding

  • SOLID encourages coding to abstractions and hiding implementation details, consistent with classic software engineering principles (Parnas, 1972).

Practical Applications and Design Patterns


Unit Testing and Testability

  • Adherence to SOLID facilitates dependency injection and small focused classes, both improving unit testing and mockability.
  • For example, DIP + constructor injection makes it straightforward to inject test doubles.

Dependency Injection and IoC Containers

  • DI (manual or via an IoC container) is a common technique to realize DIP in production systems.
  • Popular containers: Spring (Java), .NET Core DI, Guice, NestJS (TypeScript), etc.

Patterns that Support SOLID

  • Strategy, Factory, ...

Ready to see the full tree?

Clone the preview to open the complete learning structure, practice tools, and generated study materials.