• 4 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 04/08/2023

« S » pour Single Responsibility, le principe de la responsabilité unique

L'une des tâches de mon fils à la maison est de vider le lave-vaisselle. Force est de constater que cela n'inclut pas de ranger les verres dans les placards, seulement les assiettes... Il est très, très fort pour mettre en application le principe de la responsabilité unique.

En d'autres termes :

Pourquoi utiliser le principe de la responsabilité unique ?

Vous voulez comprendre ce qui se passe dans une classe et rendre cela compréhensible aussi par le reste du monde : restreindre au maximum les responsabilités de la classe permet d'y parvenir.

Tout d'abord, cela vous permet d'accéder plus facilement au code qui vous intéresse : une idée = un endroit d'implémentation. Dans notre partie de cartes, nous savons que tout ce qui est associé à une carte de jeu se retrouvera dans la classe PlayingCard.

Ensuite, les tests unitaires se rédigent beaucoup plus facilement. Même si nous n'en avons écrit aucun pour notre application, les tests de PlayingCard nécessiteraient seulement la validation des quelques éléments dont PlayingCard a la responsabilité (valeur, couleur et face visible).

Pour finir, il est facile de donner un nom à la classe, puisque les tâches de la classe sont évidentes et restreintes.

Résumons notre méthode :

  • Examiner les exigences pour déterminer ce que le code doit faire.

  • Scinder le code en responsabilités MVC.

  • Pour chaque responsabilité, veiller à ce qu'elle soit placée dans la classe appropriée. 

  • Si une classe regroupe trop de responsabilités, créer de nouvelles classes pour isoler les responsabilités les unes des autres.

Appliquez le principe à votre code

Il s'agit d'un principe facile à enfreindre. Cela se produit lorsque vous devez ajouter une nouvelle fonctionnalité à un système. Chaque brique de la fonctionnalité doit aller quelque part, n'est-ce pas ? Il peut paraître judicieux d'intégrer les nouvelles briques à des classes existantes. Mais alors, la classe devra réaliser plus qu’une seule tâche...

Examinons notre jeu de cartes. L'une des premières classes que nous avons implémentées est PlayingCard. Elle est assez simple : une valeur, une couleur et un flag indiquant si la carte est face visible ou cachée. Cette implémentation pourrait être utilisée dans de nombreux jeux de cartes différents (le poker, par exemple !). Comme dans la vraie vie ! Vous n'avez pas besoin d'un nouveau paquet de cartes pour chacun de vos jeux.

Nous souhaitons maintenant ajouter l'étape de calcul du gagnant d'une main, c'est-à-dire déterminer quel joueur a la meilleure carte. Une des approches possibles consisterait à ajouter une méthode « isBetterThan(PlayingCard other) » à la classe PlayingCard... Les PlayingCards se connaissent entre elles, donc il serait simple d'ajouter cette implémentation (moyennement satisfaisante) :

// mauvaise implémentation

// logique spécifique au jeu inclu dans le modèle

class PlayingCard {

    Rank rank;

    Suit suit;

    boolean faceUp;
     
    public bool isBetterThan(PlayingCard other) {
    
        // l'évaluation du rang et de la suite se fait ici

    }

}

Cependant, la classe disposerait ici d'une nouvelle raison de changer. Vous voyez de quoi il s'agit ?

Si les règles changent, que par exemple on décide que le cœur est la couleur la plus forte, nous devons modifier PlayingCard. Et si les règles changent à nouveau, nous modifierons à nouveau cette classe. Comme vu plus haut, dans la réalité, vous n'avez pas besoin de paquets de cartes différents selon le jeu auquel vous jouez. Le même principe doit s'appliquer à notre application.

Et donc dans le code ci-dessus, la classe a plus d'une responsabilité.

Le calcul du gagnant doit être réalisé ailleurs. Pour l'instant, ce code est intégré au contrôleur. Il enfreint le principe de responsabilité unique. Le fait de calculer le gagnant ne modifie pas le flux des interactions (la mission du contrôleur). Voyez-vous un moyen d'implémenter le calcul du gagnant, tout en respectant le principe de la responsabilité unique ?

Faites l'expérience vous-même ! 

Laissez-moi vous présenter la classe GameEvaluator. :honte: Elle dispose d'une méthode permettant d'identifier le joueur qui a la meilleure carte, elle ne fait que cela et ce sera sa seule raison de changer !

Faisons cela ensemble :

public class GameEvaluator {
    
    public Player evaluateWinner(List<Player> players);

}

En parlant de changement, nous modifierons les règles du jeu dans le prochain chapitre, et découvrirons les avantages du principe ouvert/fermé.

En résumé

  • L'expression responsabilité unique signifie qu'une classe ne doit faire qu'une chose, ou, dit autrement, n'avoir qu'une seule raison de changer.

  • Ce principe est facile à enfreindre, à mesure que vous ajoutez de nouvelles fonctionnalités au système. Lorsque vous ajoutez une nouvelle fonctionnalité, réfléchissez à ceci :

    •  Quels changements à venir peuvent avoir des répercussions sur la classe ?

    • Qu'est-ce qui pourrait conférer à la classe plusieurs raisons de changer ? 

  • N'oubliez pas : si une classe reproduit un concept de la vraie vie, elle doit uniquement implémenter la responsabilité correspondant à ce concept.

Intéressons-nous à l'ajout de fonctionnalités supplémentaires à notre système, sans modifier ce que nous avons actuellement écrit, en appliquant le principe ouvert/fermé.

Exemple de certificat de réussite
Exemple de certificat de réussite