Article original : How to Implement the SOLID Principles in Flutter and Dart
Lors de la création d'applications Flutter, il est facile de se laisser emporter par l'écriture d'un code qui fonctionne tout simplement. Mais à mesure que votre application gagne en taille et en complexité, un code mal structuré devient plus difficile à maintenir, à tester et à étendre. C'est là qu'interviennent les principes SOLID.
SOLID est un acronyme pour cinq principes de conception qui aident les développeurs à écrire un code propre, évolutif et maintenable :
S – Single Responsibility Principle (SRP / Principe de Responsabilité Unique)
O – Open/Closed Principle (OCP / Principe Ouvert/Fermé)
L – Liskov Substitution Principle (LSP / Principe de Substitution de Liskov)
I – Interface Segregation Principle (ISP / Principe de Ségrégation des Interfaces)
D – Dependency Inversion Principle (DIP / Principe d'Inversion des Dépendances)
Dans ce guide, nous allons décomposer chaque principe, expliquer sa signification et montrer des exemples pratiques de code Flutter/Dart que vous pouvez appliquer dans vos projets.
Table des matières :
Prérequis
Avant de commencer, vous devriez :
Savoir utiliser Dart et Flutter.
Avoir une compréhension de base des concepts de la POO (classes, héritage, interfaces et polymorphisme).
Avoir Flutter installé sur votre système (Guide d'installation Flutter).
Être familier avec l'exécution d'applications Flutter sur un émulateur ou un appareil physique.
Comment implémenter le Principe de Responsabilité Unique (SRP) dans Flutter
Définition : Une classe ne devrait avoir qu'une seule raison de changer. Ce principe empêche la création de « classes dieux » qui essaient de tout faire. Au lieu de cela, chaque classe doit gérer une responsabilité spécifique.
Exemple Flutter
// Principe de Responsabilité Unique (SRP)
// La classe Logger gère uniquement la journalisation
class Logger {
void log(String message) {
print(message);
}
}
// La classe UserManager gère uniquement la gestion des utilisateurs
class UserManager {
final Logger _logger;
UserManager(this._logger);
void addUser(String username) {
// Logique métier pour ajouter un utilisateur
_logger.log('Utilisateur $username ajouté.');
}
}
Explication du code
class Logger { ... }→ Cette classe est responsable uniquement de la journalisation (logging). Elle possède une méthode uniquelog.class UserManager { ... }→ Cette classe gère les utilisateurs (par exemple, leur ajout).final Logger _logger;→ Au lieu de journaliser directement,UserManagerdépend de la classeLogger.addUser(String username)→ Se concentre sur la gestion des utilisateurs, pas sur la journalisation.
En séparant les responsabilités, nous pouvons remplacer Logger par une autre implémentation (comme la sauvegarde des logs dans un fichier) sans toucher à UserManager.
Le SRP dans les projets Flutter réels :
AuthServicepour la logique d'authentificationApiServicepour les appels réseauDatabaseServicepour la persistance locale

Comment implémenter le Principe Ouvert-Fermé (OCP) dans Flutter
Définition : Les classes doivent être ouvertes à l'extension mais fermées à la modification. Cela signifie que vous ne devriez pas avoir besoin de modifier le code existant lors de l'ajout de nouvelles fonctionnalités — il suffit de l'étendre.
Exemple Flutter
// Principe Ouvert/Fermé (OCP)
// Abstraction de base
abstract class Shape {
double area();
}
// La classe Circle étend Shape
class Circle implements Shape {
final double radius;
Circle(this.radius);
@override
double area() => 3.14 * radius * radius;
}
// La classe Square étend Shape
class Square implements Shape {
final double side;
Square(this.side);
@override
double area() => side * side;
}
Explication du code
abstract class Shape→ Définit le contratarea()pour toutes les formes.class Circle implements Shape→ Étend le comportement sans modifier le code existant.class Square implements Shape→ Ajoute une autre forme de la même manière.
Si vous souhaitez ajouter un Triangle, il vous suffit de créer une nouvelle classe au lieu de modifier Shape, Circle ou Square.
L'OCP dans les projets Flutter réels :
Ajouter de nouveaux composants UI sans modifier la classe widget de base.
Supporter de nouvelles méthodes de paiement dans une application en implémentant une interface
PaymentMethod.

Comment implémenter le Principe de Substitution de Liskov (LSP) dans Flutter
Définition : Les sous-classes doivent pouvoir être substituées à leurs classes de base sans casser la fonctionnalité. Si votre fonction accepte un type de base, elle doit également accepter ses sous-types sans problème.
Exemple Flutter
// Principe de Substitution de Liskov (LSP)
void printArea(Shape shape) {
print('Surface : ${shape.area()}');
}
void main() {
Shape circle = Circle(5);
Shape square = Square(4);
printArea(circle); // Fonctionne avec Circle
printArea(square); // Fonctionne avec Square
}
Explication du code
void printArea(Shape shape)→ Fonctionne avec n'importe quelle classe implémentantShape.circleetsquare→ Les deux sont des substituts valides pourShape.
Le LSP dans les projets Flutter réels :
Un
TextFieldpeut être remplacé par un widgetPasswordField, car les deux se comportent comme des champs de saisie.Un
FirebaseAuthServicepeut être remplacé par unMockAuthServicedans les tests.

Comment implémenter le Principe de Ségrégation des Interfaces (ISP) dans Flutter
Définition : Les clients ne devraient pas dépendre de méthodes qu'ils n'utilisent pas. Au lieu d'une seule grande interface, divisez-la en interfaces plus petites et ciblées.
Exemple Flutter
// Principe de Ségrégation des Interfaces (ISP)
abstract class Flyable {
void fly();
}
abstract class Swimmable {
void swim();
}
class Bird implements Flyable {
@override
void fly() => print('L\'oiseau vole.');
}
class Fish implements Swimmable {
@override
void swim() => print('Le poisson nage.');
}
Explication du code
FlyableetSwimmable→ Contrats séparés pour le vol et la nage.Bird implements Flyable→ Les oiseaux n'ont pas besoin d'une méthodeswim.Fish implements Swimmable→ Les poissons n'ont pas besoin d'une méthodefly.
L'ISP dans les projets Flutter réels :
Diviser
AuthServiceen interfaces plus petites commeLoginService,RegistrationService,PasswordResetService.Widgets implémentant uniquement les propriétés dont ils ont réellement besoin.

Comment implémenter le Principe d'Inversion des Dépendances (DIP) dans Flutter
Définition : Les modules de haut niveau doivent dépendre d'abstractions, et non d'implémentations concrètes. Cela rend votre code plus flexible et testable.
Exemple Flutter
// Principe d'Inversion des Dépendances (DIP)
// Abstraction
abstract class Database {
void saveData(String data);
}
// Implémentation concrète
class SqlDatabase implements Database {
@override
void saveData(String data) {
print('SQL : Données sauvegardées -> $data');
}
}
// Module de haut niveau
class DataService {
final Database _database;
DataService(this._database);
void processData(String data) {
_database.saveData(data);
}
}
void main() {
Database db = SqlDatabase();
DataService service = DataService(db);
service.processData('Infos utilisateur');
}
Explication du code
abstract class Database→ Définit le contrat pour la sauvegarde des données.class SqlDatabase implements Database→ Une implémentation possible.class DataService→ Dépend uniquement de l'abstractionDatabase, et non deSqlDatabase.Database db = SqlDatabase();→ L'implémentation peut facilement être remplacée (par exemple, parFirebaseDatabase).
Le DIP dans les projets Flutter réels :
Utiliser
AuthRepositoryau lieu de lier le code directement à Firebase.Injecter des services avec
get_itouriverpod.

Tests et refactorisation avec SOLID
Les tests unitaires deviennent plus faciles puisque vous pouvez simuler (mock) les dépendances.
La refactorisation est plus fluide car les responsabilités sont bien séparées.
Les revues de code permettent de détecter tôt les violations des principes SOLID.
Réflexions finales
En suivant les principes SOLID dans Flutter et Dart :
Votre code devient plus maintenable.
Les nouvelles fonctionnalités sont plus faciles à ajouter.
Les tests deviennent beaucoup plus simples.
Ces principes ne sont pas seulement théoriques, ils améliorent directement les projets Flutter du monde réel. Commencez petit, appliquez un principe à la fois, et vous verrez rapidement votre base de code évoluer vers quelque chose de beaucoup plus évolutif et paré pour l'avenir.
Références
Robert C. Martin – Clean Architecture