Skip to content

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.

java
// 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.

java
// 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.

java
// 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.

java
// 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.

java
// 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.

java
// 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 explosion

Common 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).

Built with VitePress | Software Systems Atlas