Le Single Responsibility Principle (SRP) est le premier des principes SOLID et l’un des plus fondamentaux pour écrire un code propre, maintenable et modulaire. Ce principe impose qu’une classe ne doit remplir qu’un unique rôle bien défini. Chaque classe doit donc se concentrer sur une tâche claire et cohérente, sans en assumer d’autres fonctions annexes.
Dans cet article, nous allons explorer en profondeur le SRP en Java, en passant par des exemples concrets de mauvaises et bonnes pratiques et en montrant l’impact direct sur un projet réel.
Pourquoi appliquer le Single Responsibility Principle ?
✅ Les avantages du SRP :
- Facilite la maintenance : Une responsabilité unique signifie moins de code impacté en cas de modification.
- Améliore la testabilité : Tester une classe devient plus simple et rapide.
- Encourage la réutilisation : Les classes bien découpées peuvent être réutilisées facilement.
- Réduit le couplage : Chaque classe est indépendante des autres, ce qui évite de propager des changements inutiles.
Mais quelle est la conséquence de ne pas appliquer le SRP ?
🚫 Mauvaise pratique : une classe qui viole le SRP
Imaginons que nous ayons une application qui gère des employés. Voici une première implémentation sans SRP :
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public void printPaySlip() {
System.out.println("Pay Slip for " + name + ": $" + salary);
}
public void saveToDatabase() {
System.out.println("Saving " + name + " to database...");
}
public void sendEmail() {
System.out.println("Sending email to " + name);
}
}
🚨 Problèmes concrets dans cette implémentation :
- Difficile à modifier : Si la méthode
saveToDatabase()
doit être modifiée pour utiliser un nouveau système de stockage, on risque de perturber l’affichage des fiches de paie ou l’envoi d’e-mails. - Difficile à tester : Tester
Employee
implique de tester trois fonctionnalités différentes (base de données, impression et email). - Couplage excessif : Si un changement intervient sur un aspect (ex. migration des emails vers un autre service), toute la classe est impactée.
- Réutilisation impossible : Impossible d’utiliser
saveToDatabase()
sans embarquer les autres fonctionnalités.
Résultat : chaque modification rend le code plus fragile et difficile à maintenir.
✅ Bonne pratique : respect du SRP avec des classes distinctes
Voyons maintenant comment appliquer le SRP et quels bénéfices concrets cela apporte :
// Représentation d'un employé : Stocke uniquement les données
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
}
// Gestion de l'impression des fiches de paie
class PaySlipPrinter {
public void print(Employee employee) {
System.out.println("Pay Slip for " + employee.getName() + ": $" + employee.getSalary());
}
}
// Gestion de la persistance en base de données
class EmployeeRepository {
public void save(Employee employee) {
System.out.println("Saving " + employee.getName() + " to database...");
}
}
// Gestion de l'envoi des emails
class EmailService {
public void sendEmail(Employee employee) {
System.out.println("Sending email to " + employee.getName());
}
}
🏆 Tests avec des mocks
En appliquant le SRP, nous pouvons tester chaque classe indépendamment grâce à des mocks. Voici un exemple de test unitaire pour EmployeeRepository
en utilisant Mockito :
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
class EmployeeRepositoryTest {
@Test
void testSave() {
Employee employee = new Employee("Alice", 5000);
EmployeeRepository repository = mock(EmployeeRepository.class);
repository.save(employee);
verify(repository, times(1)).save(employee);
}
}
De la même manière, nous pouvons tester l’envoi d’un email sans envoyer de véritable email :
class EmailServiceTest {
@Test
void testSendEmail() {
Employee employee = new Employee("Bob", 4500);
EmailService emailService = mock(EmailService.class);
emailService.sendEmail(employee);
verify(emailService, times(1)).sendEmail(employee);
}
}
🎯 Bénéfices concrets des tests unitaires avec SRP
- Tests plus rapides : Chaque composant étant isolé, les tests sont plus rapides et précis.
- Détection plus rapide des bugs : On sait immédiatement quelle partie du code est en erreur.
- Tests indépendants : Modifier un système externe (ex. stockage en base) n’affecte pas les autres tests.
🔥 Conclusion
Le Single Responsibility Principle transforme un code fragile et couplé en un code structuré, modulaire et évolutif.
✅ Résumé des bénéfices concrets :
- Facilite la maintenance et les évolutions.
- Améliore la testabilité en rendant chaque classe indépendante.
- Réduit le couplage et améliore la réutilisation.
- Rend le code plus lisible et compréhensible.
Si une classe commence à gérer plusieurs responsabilités, il est temps de la diviser en classes spécialisées !
🔧 Vous voulez aller plus loin ?
Dans le prochain article, nous verrons comment appliquer l’Open/Closed Principle pour concevoir un code extensible sans modification directe !
Avez-vous rencontré des problèmes d’architecture en lien avec le SRP ? Partagez votre expérience en commentaire !