Why SOLID Matters in Java
The SOLID principles are five design guidelines introduced by Robert C. Martin that, when applied consistently, lead to software that is easier to understand, extend, and maintain. They're not abstract theory — each principle has a direct, measurable effect on code quality metrics like WMC, CBO, and LCOM. Let's explore each one with concrete Java examples.
S — Single Responsibility Principle (SRP)
A class should have only one reason to change.
A common violation is a class that both processes business logic and handles persistence:
// Violates SRP
public class Invoice {
public double calculate() { /* ... */ }
public void saveToDatabase() { /* ... */ }
public void printToPDF() { /* ... */ }
}
Refactor by extracting each responsibility into its own class: InvoiceCalculator, InvoiceRepository, and InvoicePrinter. Each class now has a single reason to change, and LCOM scores drop across the board.
O — Open/Closed Principle (OCP)
Classes should be open for extension but closed for modification.
Use interfaces and inheritance (or composition) to add new behavior without editing existing classes. The Strategy pattern is a classic implementation of OCP — add a new PaymentStrategy implementation without touching PaymentProcessor.
L — Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without altering program correctness.
A common LSP violation in Java is overriding a method to throw an exception that the parent doesn't declare:
// Violates LSP
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width); // Breaks Rectangle's contract
}
}
When LSP is violated, DIT scores rise without bringing the expected reuse benefits. Prefer composition over inheritance when a true "is-a" relationship doesn't exist.
I — Interface Segregation Principle (ISP)
No client should be forced to depend on methods it does not use.
Fat interfaces inflate CBO because implementing classes must import and satisfy methods they don't need. Split large interfaces into smaller, focused ones:
// Violates ISP
public interface Worker {
void work();
void eat();
void sleep();
}
// Follows ISP
public interface Workable { void work(); }
public interface Feedable { void eat(); }
public interface Restable { void sleep(); }
Classes implement only what they need, reducing unnecessary coupling.
D — Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
This is the most directly impactful SOLID principle for reducing CBO. Whenever a high-level service depends on a concrete low-level class, inject an interface instead:
// High-level module depends on abstraction
public class ReportService {
private final ReportRepository repository; // interface
public ReportService(ReportRepository repository) {
this.repository = repository;
}
}
Frameworks like Spring make DIP trivially easy through constructor injection.
SOLID and Metrics: The Connection
| Principle | Primary Metric Impact |
|---|---|
| SRP | Lowers WMC and LCOM |
| OCP | Stabilizes WMC over time |
| LSP | Keeps DIT meaningful |
| ISP | Reduces CBO and RFC |
| DIP | Reduces CBO significantly |
Where to Start
Don't try to apply all five principles simultaneously across a large codebase. Instead, pick your three highest-CBO or highest-WMC classes and ask: which SOLID principle does this most obviously violate? Address that first. Small, focused improvements with measurable outcomes build momentum and make the case for prioritizing code quality across the whole team.