SOLID Principles
Each principle with a violation, a refactored fix, and a visual metaphor.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
SOLID is five principles for designing classes that are easy to understand, extend, and maintain. They're the shared vocabulary of professional object-oriented design. Let's take each letter with a violation and its fix.
S - Single Responsibility Principle
A class should have one reason to change - one job.
A Swiss Army knife does many things poorly and is awkward for real cooking. A chef's knife does one thing superbly. Classes are the same: one that handles database access and email and reporting breaks in three unrelated ways. Split it.
// Violation: this class does persistence AND formatting AND emailing
class UserManager {
void save(User u) { }
String toHtml(User u) { }
void email(User u) { }
}
// Fix: one responsibility each
class UserRepository { void save(User u) { } }
class UserFormatter { String toHtml(User u) { } }
class UserMailer { void email(User u) { } }O - Open/Closed Principle
Open for extension, closed for modification. Add new behavior without editing existing, tested code.
// Violation: adding a shape means editing this method every time
double area(Object shape) {
if (shape instanceof Circle c) return Math.PI * c.r() * c.r();
if (shape instanceof Square s) return s.side() * s.side();
// ...must edit here for every new shape
}
// Fix: extend via a common interface - no edits to existing code
interface Shape { double area(); }
record Circle(double r) implements Shape {
public double area() { return Math.PI * r * r; }
}
record Triangle(double b, double h) implements Shape { // new shape, no edits
public double area() { return 0.5 * b * h; }
}L - Liskov Substitution Principle
Subtypes must be usable anywhere their parent is - without surprises.
A Square is-a Rectangle mathematically, but if setting a Square's width also changes its height, code that expects a Rectangle (set width, height stays put) breaks. The subtype violated the parent's contract. A subtype must honor what callers expect of the parent.
If overriding a method surprises callers, you've broken LSP
A subclass that throws where the parent didn't, or narrows what inputs it accepts, can't safely stand in for the parent. Design hierarchies so any subtype behaves as the parent's contract promises.
I - Interface Segregation Principle
Prefer many small interfaces over one fat one. Don't force classes to implement methods they don't need.
// Violation: a fat interface forces empty methods
interface Worker { void work(); void eat(); }
class Robot implements Worker {
public void work() { }
public void eat() { } // robots don't eat - awkward empty method
}
// Fix: split into focused interfaces
interface Workable { void work(); }
interface Eatable { void eat(); }
class Robot implements Workable { public void work() { } } // only what it needsD - Dependency Inversion Principle
Depend on abstractions, not concrete implementations. High-level code should rely on interfaces, not specific classes.
// Violation: NotificationService is welded to EmailSender
class NotificationService {
private EmailSender sender = new EmailSender(); // hard-wired
}
// Fix: depend on an interface, inject the implementation
interface MessageSender { void send(String msg); }
class NotificationService {
private final MessageSender sender;
NotificationService(MessageSender sender) { // injected
this.sender = sender;
}
}
// Now you can pass EmailSender, SmsSender, or a fake for testing.SOLID serves a goal, not the reverse
SOLID exists to make code easier to change and test - not to add ceremony. Apply each principle where it reduces pain, and don't over-engineer simple code to satisfy the acronym. Judgment matters more than dogma.
Quick check
A class handles saving users to a database, rendering them as HTML, and emailing them. Which SOLID principle does this violate?
Key takeaways
- S - Single Responsibility: a class should have one reason to change.
- O - Open/Closed: extend behavior via new types, don't modify tested code.
- L - Liskov Substitution: subtypes must be usable in place of their parent without surprises.
- I - Interface Segregation: prefer small, focused interfaces over one fat one.
- D - Dependency Inversion: depend on abstractions (interfaces), not concrete classes.
- SOLID is a means to changeable, testable code - apply it with judgment, not dogma.
SOLID is part of a broader family of timeless guidelines. Next, we cover the general engineering principles every developer should know.