En tant que développeurs, vous connaissez certainement SOLID : un ensemble de cinq principes de programmation orientée objet formulés par Robert C. Martin dans ses ouvrages sur la Clean Architecture.
Le premier de ces principes, le Single Responsibility Principle (SRP) est sans doute l'un des plus connus et également le plus largement adopté.
Mais savez-vous réellement ce qu'il signifie ?
Une traduction littérale du SRP en français donnerait : "le Principe de Responsabilité Unique". Il est même fort probable que vous ayez déjà entendu un collègue affirmer avec beaucoup d'aplomb : "Cette classe ne doit faire qu'une seule chose !"
Cette interprétation du SRP semble s'imposer d'elle même, "une seule chose" ... c'est une idée qui parait à la fois simple et évidente !
Cependant, malgré son apparente simplicité, cette vision limitée du SRP est en réalité partiellement incorrecte.
Nous allons explorer ce que signifie précisément le principe de responsabilité unique et comment l'appliquer de manière efficace dans nos projets.
Une seule chose
La méconnaissance du SRP provient probablement d'une traduction bien trop littérale de son nom et d'une mauvaise interprétation de ce que l'on entend par "une chose".
Retournons aux bases et commençons par la définition en elle même du principe, "Single Responsibility" n'est qu'un nom, sa véritable définition est la suivante :
Une classe ne doit avoir qu'une seule raison de changer
Une nuance apparaît ici : nous ne parlons plus de responsabilité, mais de raison de changer. Cette différence, subtile, peut sembler déconcertante alors essayons de l'illustrer.
Posez-vous la question suivante : quelle est la "responsabilité unique" d'une imprimante ? Vous seriez alors tenté de me répondre ... d'imprimer ! D'un point de vue centré sur la responsabilité unique, cette réponse semble tout à fait valide.
Mais si on s'intéresse aux différentes raisons qui pourraient pousser une imprimante à changer, les choses deviennent plus complexes.
Une imprimante peut être amenée à imprimer via un réseau local, ou même via internet. Elle peut également traiter différents types de formats de papier, proposer une impression en couleur ou en nuances de gris.
Toutes ces variations représentent autant de raisons de changer pour une imprimante, alors que, fondamentalement, sa responsabilité restera la même : imprimer.
Concentrer notre attention sur une soit disante responsabilité unique nous oblige à réfléchir de manière bien trop limitée alors que les raisons de changer, comme nous venons de le voir, sont multiples.
Si vous souhaitez suivre le SRP, il est nécessaire d'identifier toutes les raisons qui pourraient pousser une classe à évoluer et à encapsuler chacune de ces raisons dans une classe distincte :
1class Printer 2{ 3 public function __construct( 4 private LayoutInterface $layout, 5 private NetworkInterface $network, 6 private RenderingInterface $rendering, 7 ) {} 8 9 public function print()10 {11 //12 }13}
1$printer = new Printer(new A4Format(), new Wifi(), new Color());2$printer->print();
Désormais, notre classe respecte davantage le SRP, l'imprimante isole les différentes raisons qui pourraient la pousser à changer et orchestre un ensemble de classes ayant chacune une seule responsabilité.
Ce découpage améliorera également le découplage de l'application !
Vous avez maintenant une définition plus précise du SRP, mais il nous manque encore un élément essentiel pour appréhender totalement le principe.
Changer, mais pour qui ?
Nous avons vu jusqu'ici qu'une classe ne devait avoir qu'une seule raison de changer … mais changer pour qui ?
Jusqu'à présent, nos réflexions se portaient essentiellement sur l'aspect technique des différentes raisons qui peuvent pousser l'imprimante à évoluer : une nouvelle norme d'impression réseau, un type de papier différent, un nouveau format ...
Cependant, lorsque nous parlons de responsabilité, il ne s'agit pas seulement de définir une raison technique qui pourrait induire le changement. Il est tout aussi important de déterminer quel acteur serait responsable de ce changement.
Prenons à nouveau l'exemple de notre imprimante : un utilisateur classique et un administrateur peuvent tous deux l'utiliser, mais son comportement ne doit pas être identique selon le type d'utilisateur :
Vous seriez peut-être tenté d'ajouter différentes conditions directement dans la classe Printer
afin de distinguer les types d'utilisateurs ... mais cette approche, en plus de progressivement faire exploser la complexité de votre classe, transgresserait le SRP : notre classe auraient plusieurs raisons de changer, une par type d'utilisateur !
Ces nouvelles contraintes ne doivent pas être directement intégrées dans la classe Printer
car elles représentent des raisons supplémentaires de changement qui doivent être encapsulées ailleurs pour respecter pleinement le SRP :
1class AdminPrinter 2{ 3 public function __construct( 4 private Printer $printer, 5 ) {} 6 7 public function print() 8 { 9 /*10 * Logique d'impression propre à l'admin11 */12 13 $this->printer->print();14 }15}
L'utilisation de la composition n'est qu'à titre d'exemple, vous auriez pu utiliser de l'héritage, un template méthode ou tout autre design pattern architectural permettant le découplage
Nous oublions souvent que les acteurs eux mêmes, notre maniere d'utiliser les choses, sont une source importante de changement qui conduit nos classes à détenir de plus en plus de complexité jusqu'à les rendre impossibles à maintenir.
Conclusion
En résumé, le Principe de Responsabilité Unique ne se limite pas à attribuer une seule chose par classe mais à définir une unique raison pour laquelle cette classe pourrait évoluer.
Une bonne compréhension du SRP est essentielle pour éviter la démultiplication des responsabilités au sein d'une même classe et pour faciliter sa maintenabilité et son évolutivité.
Pour appliquer correctement le principe, il devient essentiel de réfléchir aux différentes raisons de changement et aux acteurs qui influencent ces changements.
Ces réflexions sont donc tout autant techniques que métier et nécessitent un véritable effort de conception, mais elles vous aideront à mieux organiser votre code, à réduire la complexité et à garantir une architecture à la fois solide et extensible sur le long terme.
If you think good architecture is expensive, try bad architecture. ~ Robert C. Martin
A lire
Autres articles de la même catégorie
Vous écrivez pour être lu
Les bons développeurs écrivent du code que les humains peuvent comprendre.
Mathieu De Gracia
Le véritable objectif de la responsabilité unique
Vous pensez qu'une classe ne doit faire qu'une seule chose ? Réfléchissons plus en profondeur à la réelle signification du principe de responsabilité unique (SRP)
Mathieu De Gracia
Il y aura toujours des bugs
Quelqu'un qui pense qu'une application peut-être exempte de bugs est un fou.
Mathieu De Gracia