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
| Aspect | Details |
|---|---|
| Principles Covered | SRP (Single Responsibility), OCP (Open/Closed) |
| Key Metric | LCOM (Lack of Cohesion of Methods) |
| Detection Tools | SonarQube, IntelliJ inspections |
| Refactoring Pattern | Extract Class, Strategy Pattern |
| Business Impact | 60% 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
PaymentService. It started simple:JAVA(4 lines)CodeLoading syntax highlighter...
Over three years, it grew:
JAVA(28 lines)CodeLoading syntax highlighter...
TEXT(7 lines)CodeLoading syntax highlighter...
JAVA(7 lines)CodeLoading syntax highlighter...
TEXT(4 lines)CodeLoading syntax highlighter...
π§ Mental Model: Responsibility = Reason to Change
The most common misunderstanding of SRP: thinking "responsibility" means "thing the code does."
TEXT(25 lines)CodeLoading syntax highlighter...
Employee class with methods for:calculatePay()- requested by AccountingreportHours()- requested by HRsave()- requested by IT/DBAs
Three actors = three reasons to change = SRP violation.
π¬ Deep Dive
Part 1: Single Responsibility Principle (SRP)
Detecting SRP Violations
Ask: "Who would request a change to this method?"
JAVA(18 lines)CodeLoading syntax highlighter...
LCOM measures how related the methods in a class are:
TEXT(5 lines)CodeLoading syntax highlighter...
JAVA(14 lines)CodeLoading syntax highlighter...
TEXT(6 lines)CodeLoading syntax highlighter...
- SonarQube (LCOM4 metric)
- IntelliJ (Metrics Reloaded plugin)
- JDepend
Refactoring God Classes
JAVA(18 lines)CodeLoading syntax highlighter...
JAVA(21 lines)CodeLoading syntax highlighter...
JAVA(14 lines)CodeLoading syntax highlighter...
Part 2: Open/Closed Principle (OCP)
Pattern 1: Strategy Pattern for OCP
JAVA(74 lines)CodeLoading syntax highlighter...
Pattern 2: Decorator Pattern for OCP
JAVA(50 lines)CodeLoading syntax highlighter...
Pattern 3: Plugin Architecture for OCP
JAVA(52 lines)CodeLoading syntax highlighter...
OCP with Java 17+ Sealed Classes
JAVA(25 lines)CodeLoading syntax highlighter...
- 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
JAVA(23 lines)CodeLoading syntax highlighter...
Mistake 2: Creating Abstractions for Single Implementations
JAVA(14 lines)CodeLoading syntax highlighter...
Mistake 3: Shotgun Surgery After SRP Refactoring
TEXT(4 lines)CodeLoading syntax highlighter...
JAVA(12 lines)CodeLoading syntax highlighter...
π Debug This: The Class That Everyone Feared
OrderService, something breaks. Last week, a change to shipping calculation broke the email notifications. How is that even possible?"JAVA(34 lines)CodeLoading syntax highlighter...
- Order creation logic
- Inventory management
- Payment processing
- Email notifications
- Push notifications
- Analytics tracking
- 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
π» Exercises
Exercise 1: Calculate LCOM
β Difficulty: Easy | β±οΈ Time: 15 minutes
JAVA(21 lines)CodeLoading syntax highlighter...
- How many method pairs share fields?
- How many method pairs share no fields?
- What is the LCOM score?
- How would you refactor?
TEXT(14 lines)CodeLoading syntax highlighter...
Exercise 2: Apply Strategy Pattern
ββ Difficulty: Medium | β±οΈ Time: 20 minutes
JAVA(25 lines)CodeLoading syntax highlighter...
JAVA(26 lines)CodeLoading syntax highlighter...
Exercise 3: Identify SRP Violations
ββ Difficulty: Medium | β±οΈ Time: 15 minutes
JAVA(26 lines)CodeLoading syntax highlighter...
| Responsibility | Actor | Reason to Change |
|---|---|---|
| Data querying | DBA / Data Team | Schema changes |
| PDF generation | Design Team | Layout/branding changes |
| Email sending | Marketing Team | Email templates/rules |
| S3 storage | DevOps | Infrastructure changes |
| Scheduling | Product Team | Business schedule changes |
JAVA(5 lines)CodeLoading syntax highlighter...
Exercise 4: Refactor God Class
βββ Difficulty: Medium-Hard | β±οΈ Time: 25 minutes
JAVA(21 lines)CodeLoading syntax highlighter...
JAVA(31 lines)CodeLoading syntax highlighter...
Exercise 5: Design for Extension (OCP)
ββββ Difficulty: Hard | β±οΈ Time: 30 minutes
- 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
JAVA(41 lines)CodeLoading syntax highlighter...
π€ Senior-Level Interview Questions
Question #1: How do you identify Single Responsibility violations?
I use multiple approaches:
-
Actor Analysis: Who would request changes to this class? Multiple actors = SRP violation.
-
Method Cohesion: Do methods share common data? If method groups use different fields, consider splitting.
-
LCOM Metric: I check LCOM in SonarQube. LCOM > 1 signals low cohesion.
-
Change History: I look at git history. If different parts change for different reasons, it's a violation.
-
Import Count: Many imports often indicate multiple responsibilities.
-
Test Difficulty: If testing requires mocking many unrelated dependencies, responsibilities are mixed.
Question #2: When should you NOT apply OCP?
OCP adds indirection. Sometimes that cost isn't justified:
-
Stable, rarely-changing code: If logic hasn't changed in years, abstraction is overhead.
-
Internal implementation details: Not every private method needs strategy pattern.
-
Performance-critical paths: Virtual dispatch has cost; hot paths may need concrete classes.
-
Small applications: Startup with 3 developers doesn't need plugin architecture.
-
Clear, simple logic: Two-case switch with no foreseeable additions is fine.
Question #3: Explain the relationship between SRP and microservices.
SRP at class level and microservices share the same principle at different scales:
Both aim for:
- Independent deployability
- Isolated change impact
- Clear ownership
- Start with well-factored monolith (SRP at class level)
- Identify natural service boundaries (team ownership, scaling needs)
- Extract when benefits exceed costs (independent deployment worth the complexity)
Question #4: How does SRP relate to testing?
SRP makes testing dramatically easier:
- Few dependencies to mock
- Tests focus on one behavior
- Fast test execution
- Easy to understand test intent
- Many mocks required
- Tests verify unrelated behaviors
- Slow, complex tests
- Hard to maintain
JAVA(11 lines)CodeLoading syntax highlighter...
Question #5: How do you refactor a large class without breaking existing functionality?
I follow a systematic process:
-
Characterization tests: Write tests capturing current behavior before any changes.
-
Identify clusters: Group related methods by the data they use and the actor they serve.
-
Extract one cluster at a time:
- Create new class
- Move methods
- Update callers
- Run tests
- Commit
-
Use facade: Keep original class as thin facade during transition.
-
Parallel run: Both old and new code active, compare results.
-
Deprecate original: Mark old methods as deprecated, migrate callers gradually.
Question #6: What metrics indicate healthy SRP/OCP adherence?
- 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
- 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?
- SonarQube: LCOM4, cognitive complexity, class coupling
- JDepend: Package metrics, dependency analysis
- Git: Churn analysis, co-change patterns
π Summary & Key Takeaways
Single Responsibility Principle
| Aspect | Key Point |
|---|---|
| Definition | One reason to change (one actor) |
| Detection | LCOM metric, actor analysis |
| Smell | Class changed by multiple teams |
| Refactoring | Extract Class by responsibility |
| Balance | Don't over-split cohesive logic |
Open/Closed Principle
| Aspect | Key Point |
|---|---|
| Definition | Open for extension, closed for modification |
| Implementation | Strategy, Decorator, Plugin patterns |
| Detection | Frequent modification of switch/if-else |
| Benefit | Add features without breaking existing code |
| Balance | Don't abstract prematurely |
Key Metrics
TEXT(4 lines)CodeLoading syntax highlighter...
Decision Checklist
- Multiple actors request changes
- Methods use different sets of fields
- LCOM > 2.0
- Class > 500 lines
- Testing requires many unrelated mocks
- 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.
- 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
π Review Schedule for This Article
| Day | Task | Time |
|---|---|---|
| Day 1 | Review SRP actor-based definition diagram | 5 min |
| Day 3 | Redo Exercise 1 (Calculate LCOM) | 15 min |
| Day 7 | Answer interview questions without looking | 10 min |
| Day 14 | Redo Debug This and Exercise 2 (Strategy Pattern) | 15 min |
| Day 30 | Review OCP extension points in your current project | 10 min |