DEV Community

Cover image for SOLID Principles Cheat Sheet
The Architect
The Architect

Posted on Edited on

SOLID Principles Cheat Sheet

Writing software that scales from a small monolith into a multi-team distributed system requires strict architectural discipline. The SOLID principles—coined by Robert C. Martin ("Uncle Bob")—serve as fundamental guidelines for object-oriented design and system architecture.

When improperly understood, developers often fall into two extreme traps: creating monolithic "God objects" that break with every change, or over-engineering systems into hyper-fragmented, unmaintainable micro-services.

In this deep-dive guide, we will break down each of the 5 SOLID principles from low-level class design up to high-level distributed systems design, complete with bad vs. refactored Java examples, system architecture diagrams, trade-off analyses, and a comprehensive cheat sheet.


SOLID Principles Cheat Sheet

Principle Core Concept Anti-Pattern / Code Smell Refactoring Solution
Single Responsibility (SRP) A class or module should have one, and only one, reason to change (serving one business actor/domain). God Class / Micro-Fragmentation: Classes handling payment, DB, and notifications, OR over-fragmented single-function classes. Split by domain responsibility. Use orchestrator/coordinator components for workflows.
Open/Closed (OCP) Software entities should be open for extension, but closed for modification. Conditional Bloat: Cascading if-else or switch statements checking object types or channels. Strategy Pattern, Dependency Injection, and Event-Driven Pub/Sub messaging (e.g., Kafka).
Liskov Substitution (LSP) Subtypes must be completely substitutable for their base types without breaking client behavior. Runtime Exceptions: Subclasses throwing UnsupportedOperationException or silently breaking logic. Split fat inheritance hierarchies into granular, capability-specific interfaces.
Interface Segregation (ISP) No client should be forced to depend on methods it does not use. Fat Interfaces: Monolithic interfaces forcing callers to mock or implement irrelevant methods. Role-focused, lean interfaces broken down by client requirements.
Dependency Inversion (DIP) High-level business logic must depend on abstractions, not low-level infrastructure details. Direct Instantiation: Hardcoding new ConcreteRepository() or new ThirdPartySdk() inside core logic. Pass interface dependencies via Constructor Injection (Inversion of Control).

1. Single Responsibility Principle (SRP)

"A class or module should have one, and only one, reason to change."

SRP is often misunderstood as "a class should only have one function". That is incorrect. The core idea is that a module or class should solve one business need and serve one business actor (e.g., Finance, Fulfillment, or Marketing).

   +-------------------------------------------------------------+
   |                       OrderProcessor                        |
   |-------------------------------------------------------------|
   | 1. Business Logic & Validation (Domain)                    |
   | 2. Payment API Calls          (Finance Actor)                |
   | 3. Database SQL Operations    (Fulfillment Actor)           |
   | 4. SMTP Email Delivery        (Marketing Actor)             |
   +-------------------------------------------------------------+
                                  |
                                  v  VIOLATES SRP!
   (Any change across 4 different domains forces editing this file)
Enter fullscreen mode Exit fullscreen mode

Consequences of Violating SRP

  • Hard to Debug: Cross-domain logic leads to unexpected side effects during runtime.
  • Modification Bottlenecks: Simple feature changes require navigating massive, fragile files.
  • Merge Conflicts: Multiple engineers working on different domain requirements touch the exact same file, slowing down deployments.

SRP Across Architectural Boundaries

SRP applies at three distinct levels:

  1. Class Level: Defining clear boundaries and single responsibility per class.
  2. Module Level: Grouping cohesive classes into tightly bounded packages.
  3. Microservice Level: Defining domain boundaries around single business domains.

SRP Anti-Patterns

Class-Level Anti-Pattern: Hyper-Fragmentation

  • The Mistake: Developers create separate classes like EmailValidator, AgeValidator, PhoneNumberValidator, etc.
  • Why it Hurts: It confuses "one responsibility" with "one function." It destroys readability, inflates the class count, and increases CPU in-cache misses during execution.
  • Correct Approach: A single UserValidator class that validates all parameters related to the User entity is sufficient, as all these validation rules belong to the exact same business domain.

Microservice-Level Anti-Pattern: Over-Servicicing

  • The Mistake: Creating separate microservices for EmailNotificationService, SMSNotificationService, and PushNotificationService.
  • Why it Hurts: Adds unnecessary network hop latency. In scenarios like Amazon order deliveries, where all three notifications may need to be triggered together, this creates distributed transaction overhead.
  • Correct Approach: A unified NotificationService microservice is optimal unless separate scaling requirements or distinct async guarantees demand isolation.

Code Example: SRP Refactoring