Understanding SOLID Principles

1. Understanding Single Responsibility Principle

ConceptDefinition
SRPOne reason to change per class
ActorStakeholder driving change
CohesionHigh inside class

Example: Split responsibilities

// BAD: Report does calc + format + persist
// GOOD:
class ReportCalculator { Report compute(Data d) { ... } }
class ReportFormatter  { String toPdf(Report r) { ... } }
class ReportRepository { void save(Report r) { ... } }

2. Understanding Open-Closed Principle

AspectDetail
Open forExtension via new types
Closed forModification of existing code
MechanismPolymorphism, strategy, plugin

Example: Strategy keeps it closed

interface DiscountPolicy { Money apply(Order o); }
class PricingService {
    private final List<DiscountPolicy> policies;
    Money price(Order o) {
        return policies.stream().map(p -> p.apply(o)).reduce(o.total(), Money::minus);
    }
}

3. Understanding Liskov Substitution Principle

RuleConstraint
PreconditionsCannot strengthen in subtype
PostconditionsCannot weaken in subtype
InvariantsMust preserve
ExceptionsSubtype throws same/narrower
Warning: Classic violation: Square extends Rectangle breaks setWidth/setHeight contracts.

4. Understanding Interface Segregation Principle

SmellFix
Fat interfaceSplit by client need
Forced empty implSeparate interface
Unused method on clientDifferent role interface

Example: Role interfaces

interface Reader<T> { T read(long id); }
interface Writer<T> { void write(T entity); }
// Read-only consumers depend on Reader only.

5. Understanding Dependency Inversion Principle

RuleDetail
High-levelDepends on abstractions
Low-levelImplements abstractions
DirectionBoth depend on interface

Example: Inverted dependency

// Domain owns the interface
package domain;  interface NotificationPort { void send(Message m); }
// Infra implements it
package infra;   class SmtpNotifier implements NotificationPort { ... }

6. Identifying SRP Violations

SymptomDiagnosis
"And" in class nameUserAndOrderService
Many importsTouches many concerns
Mixed abstractionHTTP + SQL in one class
High change frequencyToo many actors
Unrelated testsTest classes cover disparate behaviors

7. Applying SOLID in Domain Models

PrincipleDomain Application
SRPOne aggregate = one business concept
OCPExtend via specifications, policies
LSPSubtypes preserve invariants
ISPRole interfaces per use case
DIPDomain defines repository interfaces

8. Applying SOLID in Service Layer Design

Layer ConcernApproach
WorkflowOne service per use case (SRP)
ExtensibilityInject strategies (OCP)
SubstitutabilityInterface-driven design (LSP/DIP)
Client APINarrow service interfaces (ISP)

9. Balancing SOLID with Pragmatism

Apply Strictly When

  • Long-lived module
  • Many collaborators
  • Frequent change
  • Public API

Relax When

  • Throwaway script
  • Single user, single use
  • Prototype/spike
  • Tiny utility
Trade-offWatch For
Over-abstractionIndirection without value
Premature interfaceSingle implementer forever

10. Refactoring to SOLID

Refactor Sequence

  1. Cover with characterization tests
  2. Extract methods (find responsibilities)
  3. Extract classes (split responsibilities — SRP)
  4. Introduce interfaces at seams (DIP)
  5. Replace conditionals with polymorphism (OCP)
  6. Split fat interfaces (ISP)
  7. Verify substitutability (LSP)

11. Understanding SOLID Trade-offs

PrincipleCostBenefit
SRPMore classesFocused changes
OCPIndirectionStable core
LSPHierarchy disciplineSafe polymorphism
ISPMore interfacesDecoupled clients
DIPWiring complexityTestability, swappability

12. Implementing SOLID in Legacy Code

StepTool
Pin behaviorApproval/golden tests
Find seamsSprout method/class
Break dependencyExtract interface, parameterize constructor
Rename safelyIDE refactoring
StrangleRoute new traffic through new design