J 4 — SOLID

5 principes que vous appliquez déjà sans le savoir

Bootcode IWA-S04 — Semaine 15, Jour 4

Objectifs de la leçon

1. ConnaĂźtre les 5 principes SOLID

S, O, L, I, D et leur signification

2. ReconnaĂźtre chaque principe

Dans le code qu'on a déjà écrit

3. SOLID n'est pas nouveau

C'est nommer des bonnes pratiques

4. Identifier les violations

Dans du code existant

Plan du cours

1

SURPRISE : vous appliquez déjà SOLID

On va juste donner des noms Ă  ce que vous faites

2

S = Single Responsibility

Une classe = un rĂŽle (depuis S8)

3

O = Open/Closed

Ajouter PostgreSQL sans modifier le service (S14)

4

L = Liskov Substitution

InMemory et Postgres sont interchangeables

5

I = Interface Segregation

Des interfaces ciblées (depuis S10)

6

D = Dependency Inversion

Dépendre de l'interface, pas de l'implémentation (S11)

Module 1

SURPRISE 🎉

Vous appliquez déjà SOLID depuis des semaines

SOLID n'est PAS de la théorie abstraite

C'est le nom de ce que vous faites déjà dans le codebase Bootcode

// S8 — ResponsabilitĂ© unique

Une classe UserRepository ne fait que gérer les users

// S10 — Interfaces fines

Des interfaces ciblées plutÎt qu'un gros god-object

// S11 — DĂ©pendance aux abstractions

Le service dépend de l'interface, pas de la classe

// S14 — Open/Closed

On a ajouté Postgres sans toucher au service

💡 Aujourd'hui, on donne juste des NOMS à ces pratiques

Les 5 lettres de SOLID

S

Single Responsibility

Une classe = une seule raison de changer

O

Open/Closed

Ouvert à l'extension, fermé à la modification

L

Liskov Substitution

Une sous-classe doit remplacer sa classe parente

I

Interface Segregation

Plusieurs interfaces fines plutĂŽt qu'une grosse

D

Dependency Inversion

Dépendre des abstractions, pas des implémentations

Module 2

S

Single Responsibility

Une classe = un seul rĂŽle

S — Single Responsibility

Une classe ne doit avoir qu'une seule raison de changer

Une classe = un métier

Si une classe gĂšre les users ET envoie les emails, elle a 2 responsabilitĂ©s → 2 raisons de changer

Pourquoi ?

  • ‱ Plus facile Ă  tester
  • ‱ Moins de bugs collatĂ©raux
  • ‱ Code plus lisible
  • ‱ RĂ©utilisable

📍 On l'a vu en S8 : sĂ©parer les responsabilitĂ©s

S — Violation vs Bonne pratique

❌ God class — 3 responsabilitĂ©s

class UserService {

createUser(data) { ... }

sendWelcomeEmail() { ... }

generatePdfReport() { ... }

logToCloudWatch() { ... }

}

// 4 raisons de changer

✅ Une classe = un rîle

class UserService {

createUser(data) { ... }

}

class EmailService { ... }

class ReportService { ... }

class Logger { ... }

// 1 raison de changer chacun

S — Vous l'avez fait en S8

Dans le codebase Bootcode, chaque classe a UN rĂŽle :

// UserRepository → gùre le stockage des users

class UserRepository {

save(user) { ... }

findById(id) { ... }

delete(id) { ... }

}

// UserService → orchestre la logique mĂ©tier

class UserService {

register(data) { ... }

}

✅ UserRepository ne sait pas ce qu'est un email. UserService ne sait pas comment on stocke. Chacun son mĂ©tier.

Module 3

O

Open/Closed

Ouvert à l'extension, fermé à la modification

O — Open/Closed

Une classe doit ĂȘtre ouverte Ă  l'extension mais fermĂ©e Ă  la modification

Open = on peut étendre

Ajouter un nouveau comportement sans toucher au code existant

Closed = on ne modifie pas

Le code qui marche n'est pas touchĂ© → pas de regression

💡 Le secret ? Les interfaces et le polymorphisme

O — Ajouter Postgres : ❌ vs ✅

❌ On modifie le service existant

class UserService {

save(user) {

if (driver === 'memory') {

// ...

} else if (driver === 'pg') {

// nouveau code ajouté

}

}

}

// On a touché au code qui marchait

✅ On crĂ©e une nouvelle classe

// Le service ne change PAS

class PostgresUserRepo

implements UserRepo {

save(user) {

// SQL INSERT...

}

}

// On Ă©tend sans modifier ✅

O — Vous l'avez fait en S14

Quand on a ajouté PostgreSQL, on n'a PAS touché au UserService :

// S14 : on a juste créé une nouvelle implémentation

class PostgresUserRepository

implements UserRepository { ... }

// Le service existant n'a pas bougé d'une ligne

class UserService {

constructor(private repo: UserRepository) {}

}

✅ Open : on a ajoutĂ© Postgres. Closed : UserService n'a pas Ă©tĂ© modifiĂ©.

Module 4

L

Liskov Substitution

Une sous-classe doit pouvoir remplacer sa classe parente

L — Liskov Substitution

Si B est une sous-classe de A, alors on doit pouvoir utiliser B partout oĂč A est attendu sans casser le programme

Le contrat respecté

La sous-classe respecte les mĂȘmes promesses : mĂȘmes paramĂštres, mĂȘmes retours, pas de surprise

Nom barbara

Barbara Liskov (informaticienne, 1987) — mais l'idĂ©e est simple : pas de mauvaise surprise

💡 En pratique : InMemory et Postgres sont interchangeables grñce à l'interface

L — Violation vs Bonne pratique

❌ La sous-classe casse le contrat

class Bird {

fly() { /* vole */ }

}

class Penguin extends Bird {

fly() {

throw "Je ne vole pas!";

}

}

// Le programme qui attend un Bird casse

✅ Le contrat est respectĂ©

interface UserRepository {

save(user): void;

}

class InMemoryRepo

implements UserRepository { ... }

class PostgresRepo

implements UserRepository { ... }

// On peut swap sans casser ✅

L — InMemory et Postgres sont interchangeables

Dans Bootcode, on peut swap InMemory ↔ Postgres sans rien casser :

// Les tests utilisent InMemoryRepo

const service = new UserService(new InMemoryRepo());

// La prod utilise PostgresRepo

const service = new UserService(new PostgresRepo());

// Le service ne sait mĂȘme pas lequel il a !

✅ C'est ça Liskov : n'importe quelle implĂ©mentation de UserRepository peut remplacer une autre. Le programme continue de marcher.

Module 5

I

Interface Segregation

Des interfaces fines plutĂŽt qu'une grosse

I — Interface Segregation

Une classe ne doit pas ĂȘtre forcĂ©e d'implĂ©menter des mĂ©thodes qu'elle n'utilise pas

❌ Grosse interface

Une interface avec 20 mĂ©thodes → toutes les classes doivent tout implĂ©menter, mĂȘme ce qui ne les concerne pas

✅ Interfaces fines

DĂ©couper en petites interfaces ciblĂ©es → chaque classe implĂ©mente seulement ce qu'elle fait

📍 On l'a vu en S10 : des interfaces ciblĂ©es

I — Violation vs Bonne pratique

❌ Interface trop grosse

interface Repository {

save(); find(); delete();

sendEmail(); log();

}

// Un repo de logs doit implémenter

// sendEmail ?? Ça n'a pas de sens

✅ Interfaces ciblĂ©es

interface Readable { find(); }

interface Writable { save(); }

interface Deletable { delete(); }

// Chaque classe choisit ce qu'elle fait

class LogRepo implements Writable {}

I — Vous l'avez fait en S10

Dans Bootcode, on a des interfaces fines et ciblées :

// Pas une mega-interface qui fait tout

interface UserRepository {

save(user): void;

findById(id): User;

}

// Une autre interface pour un autre métier

interface SessionRepository { ... }

✅ UserRepository ne force pas Ă  implĂ©menter des mĂ©thodes de session. Chaque interface = un besoin prĂ©cis.

Module 6

D

Dependency Inversion

Dépendre de l'abstraction, pas de l'implémentation

D — Dependency Inversion

Les modules haut-niveau ne doivent pas dépendre des modules bas-niveau. Les deux doivent dépendre des abstractions.

❌ Couplage fort

UserService dĂ©pend directement de PostgresRepo → impossible Ă  tester sans une vraie BDD

✅ Couplage faible

UserService dĂ©pend de l'interface UserRepository → on peut injecter InMemoryRepo pour les tests

💡 C'est LA raison pour laquelle vos tests fonctionnent !

D — Violation vs Bonne pratique

❌ DĂ©pend de l'implĂ©mentation

class UserService {

private repo = new PostgresRepo();

register(data) {

this.repo.save(user);

}

}

// Impossible Ă  tester sans Postgres

✅ DĂ©pend de l'abstraction

class UserService {

constructor(

private repo: UserRepository

) {}

}

// On injecte ce qu'on veut ✅

D — Vous l'avez fait en S11

C'est LA raison pour laquelle vos tests fonctionnent :

// Dans les tests : on injecte InMemoryRepo

const repo = new InMemoryUserRepo();

const service = new UserService(repo);

// En prod : on injecte PostgresRepo

const service = new UserService(new PostgresRepo());

✅ Le service dĂ©pend de l'interface, pas de la classe. On peut tester sans BDD, dĂ©ployer avec Postgres. C'est le D en action.

PiĂšges courants

đŸ˜± "SOLID c'est compliquĂ©"

Non — vous le faites dĂ©jĂ  depuis la S8. C'est juste donner des noms Ă  vos pratiques.

🔀 Confondre les lettres

S = une classe un rÎle · O = étendre sans modifier · L = interchangeable · I = interfaces fines · D = dépendre de l'abstraction

⚡ Aller trop vite sur O et L

O et L sont plus subtils : O = on ajoute sans modifier, L = on peut swap sans casser. Relisez les exemples du codebase.

đŸ§Ș Oublier que D = tests qui marchent

Si vos tests fonctionnent avec InMemoryRepo, c'est grùce au D. C'est le bénéfice concret.

À retenir !

S

Single Responsibility

Une classe = un rĂŽle. Vu en S8 (UserRepository vs UserService).

O

Open/Closed

Étendre sans modifier. Vu en S14 (ajout de Postgres sans toucher au service).

L

Liskov Substitution

InMemory et Postgres sont interchangeables. Le service ne sait pas lequel il a.

I

Interface Segregation

Des interfaces fines et ciblées. Vu en S10.

D

Dependency Inversion

Dépendre de l'interface. Vu en S11. C'est pourquoi vos tests marchent.

🎯 SOLID = nommer ce que vous faites dĂ©jĂ . Chaque principe est visible dans le codebase Bootcode.

Questions ?

Quelle lettre vous parle le plus ?

Bootcode IWA-S04 — Semaine 15, Jour 4