Skip to content

Latest commit

 

History

History
307 lines (232 loc) · 11.5 KB

File metadata and controls

307 lines (232 loc) · 11.5 KB

 ███████╗ ██████╗ ██╗     ██╗██████╗
 ██╔════╝██╔═══██╗██║     ██║██╔══██╗
 ███████╗██║   ██║██║     ██║██║  ██║
 ╚════██║██║   ██║██║     ██║██║  ██║
 ███████║╚██████╔╝███████╗██║██████╔╝
 ╚══════╝ ╚═════╝ ╚══════╝╚═╝╚═════╝

A practical Java reference for the five SOLID design principles

with clear bad and good examples — each runnable from its own Main


Java Principles Examples License


📁 Project Structure

src/
├── srp/                              ← Single Responsibility Principle
│   ├── bad/
│   │   ├── UserBad.java
│   │   └── MainSRPBad.java
│   └── good/
│       ├── User.java
│       ├── UserRepository.java
│       ├── EmailService.java
│       ├── UserReportGenerator.java
│       └── MainSRPGood.java
├── ocp/                              ← Open/Closed Principle
│   ├── bad/
│   │   ├── AreaCalculatorBad.java
│   │   └── MainOCPBad.java
│   └── good/
│       ├── Shape.java
│       ├── Circle.java
│       ├── Rectangle.java
│       ├── Triangle.java
│       ├── AreaCalculator.java
│       └── MainOCPGood.java
├── lsp/                              ← Liskov Substitution Principle
│   ├── bad/
│   │   ├── RectangleBad.java
│   │   ├── SquareBad.java
│   │   └── MainLSPBad.java
│   └── good/
│       ├── Shape.java
│       ├── Rectangle.java
│       ├── Square.java
│       └── MainLSPGood.java
├── isp/                              ← Interface Segregation Principle
│   ├── bad/
│   │   ├── Worker.java
│   │   ├── HumanWorkerBad.java
│   │   ├── RobotWorkerBad.java
│   │   └── MainISPBad.java
│   └── good/
│       ├── Workable.java
│       ├── Feedable.java
│       ├── Sleepable.java
│       ├── HumanWorker.java
│       ├── RobotWorker.java
│       └── MainISPGood.java
└── dip/                              ← Dependency Inversion Principle
    ├── bad/
    │   ├── MySQLDatabase.java
    │   ├── UserServiceBad.java
    │   └── MainDIPBad.java
    └── good/
        ├── Database.java
        ├── MySQLDatabase.java
        ├── MongoDatabase.java
        ├── InMemoryDatabase.java
        ├── UserService.java
        └── MainDIPGood.java

Each principle folder contains a bad/ sub-package showing the violation and a good/ sub-package showing the correct approach — both with a runnable Main.


🧱 The Five Principles


S — Single Responsibility Principle

"A class should have only one reason to change."

A class that does too much is hard to maintain. When the DB logic, email logic, and report format all live in the same file, changing any one of them risks breaking the others.

Status File Responsibility
srp/bad/UserBad.java Handles user data + DB + email + reports — all in one
srp/good/User.java Holds only user data
srp/good/UserRepository.java Handles only DB persistence
srp/good/EmailService.java Handles only email sending
srp/good/UserReportGenerator.java Handles only report generation
Show key insight
❌  UserBad
     ├── saveToDatabase()      ← DB concern
     ├── sendWelcomeEmail()    ← Email concern
     └── generateReport()      ← Report concern
          — Change email template → must edit User class. Wrong.

✅  User            → data only
    UserRepository  → DB only       Each class has
    EmailService    → email only     one reason
    UserReportGenerator → reports    to change.

O — Open/Closed Principle

"Open for extension, closed for modification."

When adding a new shape requires editing the calculator class, you risk breaking existing logic. The fix: define an abstraction and let each shape own its behaviour.

Status File Description
ocp/bad/AreaCalculatorBad.java if/else instanceof chain — breaks every time a new shape is added
ocp/good/Shape.java Interface that all shapes implement
ocp/good/Circle.java · Rectangle.java · Triangle.java Each shape owns its area() logic
ocp/good/AreaCalculator.java Works with any Shapenever needs to change
Show key insight
❌  calculateArea(Object shape) {
      if (shape instanceof Circle) { ... }
      else if (shape instanceof Rectangle) { ... }
      // ← must open this file to add Triangle
    }

✅  interface Shape { double area(); }

    class Triangle implements Shape {
      public double area() { return 0.5 * base * height; }
    }
    // AreaCalculator untouched ✓

L — Liskov Substitution Principle

"Subtypes must be substitutable for their base types without altering the correctness of the program."

A Square that silently changes both sides when you set the width is not a proper Rectangle. Substituting it causes silent, wrong results.

Status File Description
lsp/bad/SquareBad.java Extends Rectangle but overrides setters to keep sides equal — breaks the area contract
lsp/bad/RectangleBad.java Base whose contract is violated by SquareBad
lsp/good/Shape.java Common abstract base — Rectangle and Square are independent
lsp/good/Rectangle.java · Square.java Each models its geometry correctly — safe to substitute
Show key insight
❌  rect.setWidth(4);
    rect.setHeight(5);
    // Expected: 20  —  Got: 25 (if rect is actually a SquareBad)

✅  List<Shape> shapes = List.of(new Rectangle(4,5), new Square(5));
    shapes.forEach(s -> s.area());   // Always correct ✓

I — Interface Segregation Principle

"Clients should not be forced to depend on interfaces they do not use."

A fat interface forces RobotWorker to implement eat() and sleep() — methods that make no sense for a machine — leading to empty stubs or runtime exceptions.

Status File Description
isp/bad/Worker.java Fat interface: work() + eat() + sleep() — all or nothing
isp/bad/RobotWorkerBad.java Forced to throw UnsupportedOperationException for eat() and sleep()
isp/good/Workable.java Focused interface — just work()
isp/good/Feedable.java Focused interface — just eat()
isp/good/Sleepable.java Focused interface — just sleep()
isp/good/RobotWorker.java Implements only Workable — no stubs, no surprises
Show key insight
❌  class RobotWorkerBad implements Worker {
      public void eat()   { throw new UnsupportedOperationException(); }
      public void sleep() { throw new UnsupportedOperationException(); }
    }

✅  class RobotWorker implements Workable {
      public void work() { System.out.println("Robot working..."); }
      // Done. Nothing else needed.
    }

D — Dependency Inversion Principle

"Depend on abstractions, not concretions. High-level modules should not depend on low-level modules."

Hardcoding new MySQLDatabase() inside business logic couples your service to a specific technology. Swapping to MongoDB — or even mocking for tests — requires editing the business class itself.

Status File Description
dip/bad/UserServiceBad.java Hardcodes new MySQLDatabase() — impossible to swap or test
dip/good/Database.java Abstraction both layers depend on
dip/good/UserService.java Receives any Database via constructor injection
dip/good/MySQLDatabase.java · MongoDatabase.java Concrete implementations — swappable at the call site
dip/good/InMemoryDatabase.java Drop-in for fast, isolated unit tests — no real DB needed
Show key insight
❌  class UserServiceBad {
      private MySQLDatabase db = new MySQLDatabase(); // hardwired
    }

✅  class UserService {
      private final Database db;
      public UserService(Database db) { this.db = db; } // injected
    }

    // At runtime:   new UserService(new MySQLDatabase())
    // In tests:     new UserService(new InMemoryDatabase())  ✓

▶️ Running the Examples

Every bad/ and good/ folder has its own Main. Run them side by side to see the contrast.

# ─── SRP ───────────────────────────────────────────
javac srp/bad/*.java  && java srp.bad.MainSRPBad
javac srp/good/*.java && java srp.good.MainSRPGood

# ─── OCP ───────────────────────────────────────────
javac ocp/bad/*.java  && java ocp.bad.MainOCPBad
javac ocp/good/*.java && java ocp.good.MainOCPGood

# ─── LSP ───────────────────────────────────────────
javac lsp/bad/*.java  && java lsp.bad.MainLSPBad
javac lsp/good/*.java && java lsp.good.MainLSPGood

# ─── ISP ───────────────────────────────────────────
javac isp/bad/*.java  && java isp.bad.MainISPBad
javac isp/good/*.java && java isp.good.MainISPGood

# ─── DIP ───────────────────────────────────────────
javac dip/bad/*.java  && java dip.bad.MainDIPBad
javac dip/good/*.java && java dip.good.MainDIPGood

💡 Or simply open the project in IntelliJ IDEA or VS Code and run any Main class directly with the ▶️ gutter button.


📚 Further Reading


Made with ☕ by Mazennaji