Java

SOLID: SRP and OCP

SOLID principles are the foundation of object-oriented design. Yet most developers can recite them but struggle to apply them. This article dives deep into the first two principles - Single Responsibility and Open/Closed - with practical examples, metrics for detection, and real refactoring patterns.

πŸ“‹ At a Glance

AspectDetails
Principles CoveredSRP (Single Responsibility), OCP (Open/Closed)
Key MetricLCOM (Lack of Cohesion of Methods)
Detection ToolsSonarQube, IntelliJ inspections
Refactoring PatternExtract Class, Strategy Pattern
Business Impact60% reduction in change-related bugs

🎯 What You'll Learn

After reading this article, you will be able to:

  • Define "responsibility" precisely using the actor-based definition
  • Measure cohesion using LCOM and other metrics
  • Identify God Classes and refactor them systematically
  • Apply OCP using Strategy, Decorator, and Plugin patterns
  • Avoid over-application of SOLID (when to break the rules)
  • Answer senior interview questions about SOLID with depth

πŸ”₯ Production Story: The 47-Responsibility Service

A payment processing company had a class called PaymentService. It started simple:
JAVA(4 lines)
Code
Loading syntax highlighter...

Over three years, it grew:

JAVA(28 lines)
Code
Loading syntax highlighter...
The problems:
TEXT(7 lines)
Code
Loading syntax highlighter...
The cost: Every change required understanding the entire 3,400-line file. Every test run took 12 minutes. Every bug fix risked breaking something else. Five different teams (Payments, Fraud, Reporting, Compliance, Analytics) had to coordinate for any change.
The fix took 3 weeks:
JAVA(7 lines)
Code
Loading syntax highlighter...
The results:
TEXT(4 lines)
Code
Loading syntax highlighter...

🧠 Mental Model: Responsibility = Reason to Change

The most common misunderstanding of SRP: thinking "responsibility" means "thing the code does."

Wrong interpretation: "This class does one thing - it handles payments."
Correct interpretation: "This class has one reason to change - changes requested by one actor/stakeholder."
TEXT(25 lines)
Code
Loading syntax highlighter...
Example: An Employee class with methods for:
  • calculatePay() - requested by Accounting
  • reportHours() - requested by HR
  • save() - requested by IT/DBAs

Three actors = three reasons to change = SRP violation.


πŸ”¬ Deep Dive

Part 1: Single Responsibility Principle (SRP)

Detecting SRP Violations

Method 1: The Actor Test

Ask: "Who would request a change to this method?"

JAVA(18 lines)
Code
Loading syntax highlighter...
Method 2: LCOM Metric (Lack of Cohesion of Methods)

LCOM measures how related the methods in a class are:

TEXT(5 lines)
Code
Loading syntax highlighter...
Example calculation:
JAVA(14 lines)
Code
Loading syntax highlighter...
TEXT(6 lines)
Code
Loading syntax highlighter...
Tools that calculate LCOM:
  • SonarQube (LCOM4 metric)
  • IntelliJ (Metrics Reloaded plugin)
  • JDepend

Refactoring God Classes

Step-by-step process:
Step 1: Identify responsibility clusters
JAVA(18 lines)
Code
Loading syntax highlighter...
Step 2: Create focused classes
JAVA(21 lines)
Code
Loading syntax highlighter...
Step 3: Create facade if needed
JAVA(14 lines)
Code
Loading syntax highlighter...

Part 2: Open/Closed Principle (OCP)

Definition: Software entities should be open for extension but closed for modification.
Translation: Add new behavior without changing existing code.

Pattern 1: Strategy Pattern for OCP

JAVA(74 lines)
Code
Loading syntax highlighter...

Pattern 2: Decorator Pattern for OCP

JAVA(50 lines)
Code
Loading syntax highlighter...

Pattern 3: Plugin Architecture for OCP

JAVA(52 lines)
Code
Loading syntax highlighter...

OCP with Java 17+ Sealed Classes

JAVA(25 lines)
Code
Loading syntax highlighter...
When to use Sealed Classes vs Strategy:
  • Sealed Classes: Fixed set of types, rarely adding new ones
  • Strategy: Open set, frequently adding new implementations

⚠️ Common Mistakes

Mistake 1: One Method Per Class

Problem: Taking SRP too literally.
JAVA(23 lines)
Code
Loading syntax highlighter...
Guideline: Group methods that change together for the same reason.

Mistake 2: Creating Abstractions for Single Implementations

Problem: OCP dogma creating unnecessary indirection.
JAVA(14 lines)
Code
Loading syntax highlighter...
Rule: Create interface when you need multiple implementations OR for testing boundaries.

Mistake 3: Shotgun Surgery After SRP Refactoring

Problem: Over-splitting causes changes to touch many files.
TEXT(4 lines)
Code
Loading syntax highlighter...
Solution: Split by business capability, not by technical layer.
JAVA(12 lines)
Code
Loading syntax highlighter...

πŸ› Debug This: The Class That Everyone Feared

A developer reports: "Every time we touch OrderService, something breaks. Last week, a change to shipping calculation broke the email notifications. How is that even possible?"
JAVA(34 lines)
Code
Loading syntax highlighter...
Why does changing shipping break email? Find the root cause!

βœ… Solution:
The root cause is SRP violation creating hidden coupling. The class has at least 6 different responsibilities:
  1. Order creation logic
  2. Inventory management
  3. Payment processing
  4. Email notifications
  5. Push notifications
  6. Analytics tracking
Why shipping broke email: When all code is in one class, refactoring one method often requires touching shared dependencies. The developer likely:
  • Renamed a private method that email code also used
  • Changed the order of operations, affecting notification timing
  • Modified a shared data structure that both shipping and email relied on
The lesson: SRP violations create invisible coupling. When a class does multiple things, changes to one responsibility ripple to others in unexpected ways. The solution is to split by actor/responsibility.

πŸ’» Exercises

Exercise 1: Calculate LCOM

⭐ Difficulty: Easy | ⏱️ Time: 15 minutes

Task: Calculate LCOM for this class and determine if it violates SRP.
JAVA(21 lines)
Code
Loading syntax highlighter...
Questions:
  1. How many method pairs share fields?
  2. How many method pairs share no fields?
  3. What is the LCOM score?
  4. How would you refactor?
βœ… Solution:
TEXT(14 lines)
Code
Loading syntax highlighter...

Exercise 2: Apply Strategy Pattern

⭐⭐ Difficulty: Medium | ⏱️ Time: 20 minutes

Task: Refactor this switch statement using Strategy pattern.
JAVA(25 lines)
Code
Loading syntax highlighter...
βœ… Solution:
JAVA(26 lines)
Code
Loading syntax highlighter...

Exercise 3: Identify SRP Violations

⭐⭐ Difficulty: Medium | ⏱️ Time: 15 minutes

Task: Identify all SRP violations and list the actors for each responsibility.
JAVA(26 lines)
Code
Loading syntax highlighter...
βœ… Solution:
ResponsibilityActorReason to Change
Data queryingDBA / Data TeamSchema changes
PDF generationDesign TeamLayout/branding changes
Email sendingMarketing TeamEmail templates/rules
S3 storageDevOpsInfrastructure changes
SchedulingProduct TeamBusiness schedule changes
5 actors = 5 classes needed:
JAVA(5 lines)
Code
Loading syntax highlighter...

Exercise 4: Refactor God Class

⭐⭐⭐ Difficulty: Medium-Hard | ⏱️ Time: 25 minutes

Task: Refactor this God Class into cohesive services.
JAVA(21 lines)
Code
Loading syntax highlighter...
βœ… Solution:
JAVA(31 lines)
Code
Loading syntax highlighter...

Exercise 5: Design for Extension (OCP)

⭐⭐⭐⭐ Difficulty: Hard | ⏱️ Time: 30 minutes

Task: Design a payment system that follows OCP - new payment methods can be added without modifying existing code.
Requirements:
  • Support Credit Card, PayPal, Bank Transfer
  • Easy to add new methods (Crypto, Apple Pay)
  • Each method has different validation rules
  • Each method has different fee calculation
βœ… Solution:
JAVA(41 lines)
Code
Loading syntax highlighter...

🎀 Senior-Level Interview Questions

Question #1: How do you identify Single Responsibility violations?

Strong answer:

I use multiple approaches:

  1. Actor Analysis: Who would request changes to this class? Multiple actors = SRP violation.
  2. Method Cohesion: Do methods share common data? If method groups use different fields, consider splitting.
  3. LCOM Metric: I check LCOM in SonarQube. LCOM > 1 signals low cohesion.
  4. Change History: I look at git history. If different parts change for different reasons, it's a violation.
  5. Import Count: Many imports often indicate multiple responsibilities.
  6. Test Difficulty: If testing requires mocking many unrelated dependencies, responsibilities are mixed.

Question #2: When should you NOT apply OCP?

Strong answer:

OCP adds indirection. Sometimes that cost isn't justified:

  1. Stable, rarely-changing code: If logic hasn't changed in years, abstraction is overhead.
  2. Internal implementation details: Not every private method needs strategy pattern.
  3. Performance-critical paths: Virtual dispatch has cost; hot paths may need concrete classes.
  4. Small applications: Startup with 3 developers doesn't need plugin architecture.
  5. Clear, simple logic: Two-case switch with no foreseeable additions is fine.
My rule: Apply OCP when I see a pattern of extension (3+ similar changes) or when I can't predict future requirements.

Question #3: Explain the relationship between SRP and microservices.

Strong answer:

SRP at class level and microservices share the same principle at different scales:

Class SRP: One reason to change β†’ one actor/stakeholder Service SRP: One business capability β†’ one team

Both aim for:

  • Independent deployability
  • Isolated change impact
  • Clear ownership
Key difference: Microservice boundaries have higher cost (network, data consistency). You need stronger justification than class splitting.
My approach:
  1. Start with well-factored monolith (SRP at class level)
  2. Identify natural service boundaries (team ownership, scaling needs)
  3. Extract when benefits exceed costs (independent deployment worth the complexity)

Question #4: How does SRP relate to testing?

Strong answer:

SRP makes testing dramatically easier:

High cohesion (good SRP):
  • Few dependencies to mock
  • Tests focus on one behavior
  • Fast test execution
  • Easy to understand test intent
Low cohesion (poor SRP):
  • Many mocks required
  • Tests verify unrelated behaviors
  • Slow, complex tests
  • Hard to maintain
Example:
JAVA(11 lines)
Code
Loading syntax highlighter...

Question #5: How do you refactor a large class without breaking existing functionality?

Strong answer:

I follow a systematic process:

  1. Characterization tests: Write tests capturing current behavior before any changes.
  2. Identify clusters: Group related methods by the data they use and the actor they serve.
  3. Extract one cluster at a time:
    • Create new class
    • Move methods
    • Update callers
    • Run tests
    • Commit
  4. Use facade: Keep original class as thin facade during transition.
  5. Parallel run: Both old and new code active, compare results.
  6. Deprecate original: Mark old methods as deprecated, migrate callers gradually.
Key: Small, reversible steps. Never refactor and add features simultaneously.

Question #6: What metrics indicate healthy SRP/OCP adherence?

Strong answer:
For SRP:
  • LCOM (Lack of Cohesion): < 1.0 ideal, > 2.0 needs attention
  • Class size: < 200 lines typically, > 500 is red flag
  • Method count: < 15 public methods
  • Change coupling: Changes affect single class, not cascade
For OCP:
  • Instability metric: Ce/(Ca+Ce) should be near 0 for stable abstractions
  • Change pattern: Adding features = adding classes, not modifying existing
  • Extension frequency: How often do you add new implementations?
Tools:
  • SonarQube: LCOM4, cognitive complexity, class coupling
  • JDepend: Package metrics, dependency analysis
  • Git: Churn analysis, co-change patterns

πŸ“ Summary & Key Takeaways

Single Responsibility Principle

AspectKey Point
DefinitionOne reason to change (one actor)
DetectionLCOM metric, actor analysis
SmellClass changed by multiple teams
RefactoringExtract Class by responsibility
BalanceDon't over-split cohesive logic

Open/Closed Principle

AspectKey Point
DefinitionOpen for extension, closed for modification
ImplementationStrategy, Decorator, Plugin patterns
DetectionFrequent modification of switch/if-else
BenefitAdd features without breaking existing code
BalanceDon't abstract prematurely

Key Metrics

TEXT(4 lines)
Code
Loading syntax highlighter...

Decision Checklist

When to split a class (SRP):
  • Multiple actors request changes
  • Methods use different sets of fields
  • LCOM > 2.0
  • Class > 500 lines
  • Testing requires many unrelated mocks
When to apply OCP:
  • 3+ similar switch/if-else modifications
  • Adding new types is common operation
  • Want plugin architecture
  • Need to modify behavior without source changes

🏁 Conclusion

SRP and OCP are the foundation of maintainable code. SRP keeps classes focused and testable. OCP enables extension without risk.

Key insights:
  • SRP is about actors, not about "doing one thing"
  • OCP is about extension points, not about making everything abstract
  • Both require judgment - over-application is as harmful as under-application
  • Metrics help - LCOM, class size, change coupling
Your next step: Continue to Part 3 (SOLID - LSP, ISP, DIP) to complete your understanding of SOLID principles.

πŸ“… Review Schedule for This Article

DayTaskTime
Day 1Review SRP actor-based definition diagram5 min
Day 3Redo Exercise 1 (Calculate LCOM)15 min
Day 7Answer interview questions without looking10 min
Day 14Redo Debug This and Exercise 2 (Strategy Pattern)15 min
Day 30Review OCP extension points in your current project10 min

Next in series: [Part 3: SOLID - Liskov, Interface Segregation, Dependency Inversion]