Les principes SOLID sont cinq règles incontournables en programmation orientée objet. Créés par Robert C. Martin (Uncle Bob), ils t'aident à écrire du code Flutter :

  • Plus facile à maintenir
  • Plus simple à tester
  • Plus évolutif
  • Plus robuste

📌 1. Single Responsibility Principle (SRP)

Une classe = Une seule responsabilité.

Pourquoi c'est important ?

  • Ton code devient plus clair, lisible et simple à modifier.

🔴 Mauvais exemple :

class UserService {
  void login(String email, String password) {}
  void logout() {}
  void saveUser(User user) {}
}
⚠️ Problème : mélange authentification et sauvegarde des données.

🟢 Bon exemple :

class AuthService {
  void login(String email, String password) {}
  void logout() {}
}

class UserRepository {
  void saveUser(User user) {}
}
👍 Avantage : Chaque classe gère une responsabilité précise :
AuthService : authentification uniquement.
UserRepository : gestion des données utilisateur.

📌 2. Open-Closed Principle (OCP)

Ouvert aux extensions, mais fermé aux modifications.

Pourquoi c'est important ?

  • Tu peux améliorer ton app sans risquer d'introduire de nouveaux bugs.

🔴 Mauvais exemple :

double calculateArea(Shape shape) {
  if (shape is Square) return shape.side * shape.side;
  if (shape is Circle) return pi * shape.radius * shape.radius;

  throw UnsupportedError('Forme inconnue');
}
⚠️ Problème : ajouter une nouvelle forme oblige à modifier ce code.

🟢 Bon exemple :

abstract class Shape {
  double area();
}

class Square implements Shape {
  final double side;
  Square(this.side);

  @override
  double area() => side * side;
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);

  @override
  double area() => pi * radius * radius;
}
👍 Avantage : Ajouter une nouvelle forme n’impliquera plus de modifier le code existant.

📌 3. Liskov Substitution Principle (LSP)

Les classes dérivées doivent pouvoir remplacer leurs classes parentes sans altérer leur comportement.

Pourquoi c'est important ?

  • Cela garantit la cohérence et évite les bugs subtils.

🔴 Mauvais exemple :

class Bird {
  void fly() {}
}

class Penguin extends Bird {
  @override
  void fly() => throw Exception('Pingouin ne vole pas !');
}
⚠️ Problème : Un pingouin ne peut pas remplacer correctement un oiseau volant.

🟢 Bon exemple :

abstract class Bird {}

abstract class FlyingBird extends Bird {
  void fly();
}

class Sparrow extends FlyingBird {
  @override
  void fly() => print('vole');
}

class Penguin extends Bird {
  void swim() => print('nage');
}
👍 Avantage : Plus de clarté et pas de comportements inattendus.

📌 4. Interface Segregation Principle (ISP)

Préférer plusieurs petites interfaces spécialisées plutôt qu'une grosse interface unique.

Pourquoi c'est important ?

  • Les classes implémentent uniquement les méthodes dont elles ont vraiment besoin.

🔴 Mauvais exemple :

abstract class UserActions {
  void login();
  void logout();
  void deleteUser();
}

class SimpleUser implements UserActions {
  void login() {}
  void logout() {}
  void deleteUser() => throw UnimplementedError();
}
⚠️ Problème : SimpleUser implémente inutilement une méthode qu’il n’utilise jamais.

🟢 Bon exemple :

abstract interface class AuthActions {
  void login();
  void logout();
}

abstract interface class AdminActions {
  void deleteUser();
}

class SimpleUser implements AuthActions {
  void login() {}
  void logout() {}
}
👍 Avantage : Chaque classe est spécialisée et claire.

📌 5. Dependency Inversion Principle (DIP)

Dépendre des abstractions plutôt que des implémentations concrètes.

Pourquoi c'est important ?

  • Ton code devient découplé, flexible et facile à tester.

🔴 Mauvais exemple :

class UserService {
  final FirebaseAuth auth;

  UserService(this.auth);
}
⚠️ Problème : Le module métier dépend directement d’une implémentation concrète (Firebase).

🟢 Bon exemple :

abstract interface class AuthRepository {
  Future<void> login();
}

class FirebaseAuthRepository implements AuthRepository {
  Future<void> login() {}
}

class UserService {
  final AuthRepository authRepository;

  UserService(this.authRepository);
}
👍 Avantage : Si demain Firebase est remplacé par Supabase, aucune modification du service métier ne sera nécessaire.

🎯 Résumé rapide :

Lettre Principe Résumé simple
S Single Responsibility Une classe doit avoir une seule responsabilité
O Open-Closed Ouvert aux ajouts, fermé aux modifications
L Liskov Substitution Remplaçable sans changer de comportement
I Interface Segregation Petites interfaces spécialisées
D Dependency Inversion Dépendre des abstractions, pas des implémentations