Metadata Card
- Prerequisites: Chapter 2 (Abstract Data Types)
- Estimated Time: 40 minutes
- Core Difficulty: Foundation-building
- Completion Milestone: Master SOLID principles and be able to identify violations in real code
Your Progress
The forge runs, tests are green, CI/CD is automated. But some codebases feel "easy to change" while others feel "like turning an oil tanker." The difference isn't technology — it's design principles.
The City of Artisans teaches: "Build each part so it can be replaced without rebuilding the whole machine." SOLID principles are the how.
First Layer: Single Responsibility Principle (SRP)
A class should have one, and only one, reason to change.
// Violation — Tournament class knows too much
public class Tournament {
public void create() { /* ... */ }
public void save() { /* SQL here */ }
public void sendNotification() { /* email logic */ }
public String formatReport() { /* HTML generation */ }
}
// Better — separated responsibilities
public class Tournament { /* domain logic only */ }
public class TournamentRepository { /* persistence */ }
public class TournamentNotifier { /* notifications */ }
public class TournamentReport { /* formatting */ }Second Layer: Open-Closed Principle (OCP)
Software entities should be open for extension, closed for modification.
// Bad — adding a new scoring type requires changing this class
public class ScoreCalculator {
public int calculate(String type, int score) {
if ("normal".equals(type)) return score;
if ("ranked".equals(type)) return score * 2;
// Add new type here means modifying this class
}
}
// Good — extend without modifying
public interface ScoringRule {
int apply(int baseScore);
}
public class NormalScoring implements ScoringRule { ... }
public class RankedScoring implements ScoringRule { ... }
// Add new: class TeamScoring implements ScoringRule { }Third Layer: Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types.
// Violation
public class Rectangle {
private int w, h;
public void setWidth(int w) { this.w = w; }
public void setHeight(int h) { this.h = h; }
public int getArea() { return w * h; }
}
public class Square extends Rectangle {
@Override
public void setWidth(int w) {
super.setWidth(w);
super.setHeight(w); // Side effect!
}
}
// Client expects Rectangle behavior
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(10);
// r.getArea() = 100 (not 50) — violation!If a subclass changes base class behavior in ways the client doesn't expect, it breaks LSP.
Fourth Layer: Interface Segregation Principle (ISP)
No client should be forced to depend on methods it does not use.
// Bad — fat interface
public interface TournamentService {
void create();
void delete();
void sendNotifications();
void calculatePrizes();
void generateReport();
}
// A client only needs "create" — but depends on all methods
// Good — segregated
public interface TournamentCreator { void create(); }
public interface TournamentDeleter { void delete(); }
public interface PrizeCalculator { void calculate(); }Fifth Layer: Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
// Violation — high-level depends on low-level
public class TournamentService {
private PostgreSQLRepository repo; // Direct dependency!
}
// Good — both depend on abstraction
public interface TournamentRepository {
void save(Tournament t);
}
public class TournamentService {
private TournamentRepository repo; // depends on interface
}
public class PostgreSQLRepository implements TournamentRepository { }Sixth Layer: DRY, YAGNI, and KISS
DRY (Don't Repeat Yourself): Every piece of knowledge should have a single, unambiguous representation.
YAGNI (You Aren't Gonna Need It): Don't add functionality until it's necessary.
KISS (Keep It Simple, Stupid): Complexity is easy; simplicity takes effort.
Seventh Layer: Composition Over Inheritance
Prefer composing objects to extending classes. Inheritance couples classes tightly; composition lets you swap parts.
// Inheritance
public class Tournament { }
public class PvpTournament extends Tournament { }
public class TimedPvpTournament extends PvpTournament { }
// Explodes combinatorially
// Composition
public interface Mode { /* pvp, pve, etc */ }
public interface Timer { /* timed, untimed */ }
public class Tournament {
private Mode mode;
private Timer timer;
}
// Flexible — add modes without class explosionCommon Pitfalls: Making classes so small for SRP that you have 200 micro-classes. Using inheritance when composition would suffice. Over-applying patterns to simple problems.
Passing Challenges
- Warm up: Find a class in your project that violates SRP (does at least 2 unrelated things). Split it.
- Challenge: Find an interface with many methods where clients use only a subset. Apply ISP to split it.
- Observe: In an open-source project, find a class hierarchy. Could it be replaced with composition?
Traveler's Notes
SOLID, DRY, YAGNI, KISS, composition over inheritance — these are not rules carved in stone, they're heuristics based on decades of experience. They help you build code that resists entropy — code that's easy to change, extend, and test. Apply them selectively, not religiously.
Next: Creational Patterns (Chapter 8).