Le principe de responsabilité unique | 2 vote(s)
Par Emmanuel Deloget, jeudi 7 septembre 2006 à 12:00 :: Architecture Orientée Objet :: permalien #16
Tags: principe POO, SRP
Intuitivement, on a tendance à considérer un objet et ses méthodes comme un tout. Ainsi, si on considère l'objet Image on pense immédiatement à sa représentation en mémoire (son modèle mathématique) mais aussi à l'affichage de cette image à l'écran et à la sauvegarde de celle-ci dans un fichier. Ce faisant, on introduit dans la classe Image plusieurs responsabilité qui vont plus tard nous porter préjudice.
Pourquoi ?
Il y a en fait deux questions ici: qu'est-ce qu'une responsabilité, et pourquoi avoir plusieurs responsabilités regroupées dans une seule classe nuit-il au design de l'application ?
DeMarco[1], le scientifique qui le premier a énoncé ce principe, définit une reponsabilité comme étant "une raison de changement", c'est à dire un sous-sytème qui peut forcer la modification du code existant en cas de changement. On verra un exemple simple qui explicitera mieux le phénomène plus loins dans ce texte.
De fait, avoir plusieurs responsabilités dans une seule classe implique que plusieurs sous-systèmes peuvent être à l'origine de modifications dans cette classe - et celà réponds à notre seconde question. Cela signifie que j'introduis une dépendance qui n'a peut-être pas lieu d'être dans ma hiérarchie de classe.
class Image
{
public:
bool sauve(const std::string& nomfichier);
void affiche(Fenetre *fenetre);
};
Supposons que l'interface du système d'affichage change: dans ce cas, je vais probablement devoir modifier ma méthode affiche() - clairement, il s'agit là d'une responsabilité, au sens ou nous l'avons défini plus haut - il y a un lien fort entre la fonction d'affichage et le système de fenêtrage.
Supposons maintenant que la classe Image soit utilisée dans un utilitaire en ligne de commande - qui n'a donc pas besoin d'affichage. Comment expliquer logiquement la dépendance entre cet utilitaire et le système de fenêtrage ? De plus, maintenant que nous avons du modifier la méthode affiche() nous avons besoin de recompiler cet utilitaire, malgré le fait que les modifications ne sont pas censées l'impacter.
Le même problème se pose dans l'autre sens si nous avons besoin de modifier la façon dont la sérialisation se fait - là aussi, nous introduisons une dépendance due au fait que la classe Image a au moins une responsabilité de trop.
Cet exemple simple montre bien les limitations induites par une classe ayant plusieurs responsabilités.
Corriger le problème
Il est aisé de dire qu'une classe ne doit avoir qu'une unique responsabilité, il est beaucoup plus complexe de ne pas tomber dans le piège des responsabilités multiples car un design intuitif nous ammène presque toujours vers cette erreur. Au final, une analyse fine doit être effectuée pour déterminer les problèmes potentiels.
Reprenons notre exemple de la classe Image. Dans un monde parfait, la classe image est uniquement responsable de l'objet mathématique sous-jacent:
class Image
{
public:
int bits_par_pixels() const;
int largeur() const;
int hauteur() const;
char *ptr_data();
};
Nous avons alors besoin de deux classes ayant chacun une unique responsabilité: la première va gérer l'affichage tandis que la seconde va gérer la sérialisation de l'image:
class AfficheurImage
{
public:
void affiche(Fenetre *fenetre, Image *image);
};
class SerialiseurImage
{
public:
Image *lit(const std::string& nom_fichier);
bool sauve(const std::string& nom_fichier, Image *image);
};
De cette manière, la gestion de l'image est indépendante de l'affichage ou de la sérialisation, et ces deux composants sont indépendants entre eux : nous avons ainsi permit une plus grande réutilisabilité des trois composantes.
Il y a bien évidemment un prix à payer pour l'application de ce principe: si je ne souhaite avoir qu'une unique responsabilité par classe alors le nombre de classe de mon système va augmenter en conséquence. D'un autre coté, ces classes seront de taille modeste et elle auront pour la plupart un niveau très élevé de réusabilité.
Conclusion
Si une classe a plusieurs responsabilités, elle induit non seulement des problèmes de maintenance liées à l'évolution de l'implémentation mais aussi des dépendances qui peuvent ne pas être souhaitable. Au mieux, ces dépendances réduisent les possibilités de réutilisation d'une classe, au pire ces dépendances peuvent créer des cauchemars lors de l'intégration. Ce sont les deux raisons prnicipales qui ont mené au principe de responsabilité unique:
Une classe ne doit avoir qu'une unique raison d'être modifiée
Cette conclusion me permet de noter un point important à propos du design orienté objet: l'un de ses plus grands atouts est de réduire les dépendances entre les différents modules qui composent un logiciel de manière à augmenter le potentiel de réutilisabilité du code. Ce point avait déja été très vaguement abordé dans un billet précédent, et les suivants ne vont faire que le mettre davantage en exergue.
Notes
[1] Structured Analysis and System Specification, Tom DeMarco, 1979, Yourdon Press Computing Serie ; le nom que DeMarco donne au principe de responsabilité unique est principe de cohésion
Commentaires
1. Le vendredi 15 septembre 2006 à 00:55, par Christophe Moustier
2. Le vendredi 15 septembre 2006 à 12:12, par Emmanuel Deloget
3. Le mercredi 15 octobre 2008 à 23:34, par djflex68
:: Fil rss des commentaires de ce billet ::
Ajouter un commentaire