POODR Chapitre 2 : Concevoir des classes à responsabilité unique
Tony Duong
juil. 15, 2026 ・ 5 min
Vue d'ensemble
Le chapitre 2 traite du premier objectif, le plus fondamental : construire des classes qui ne font qu'une seule chose. Une classe à responsabilité unique est facile à réutiliser et facile à modifier, car tout ce qu'elle contient change pour la même raison. Ce chapitre utilise le désormais célèbre exemple de bicyclette Gear/Wheel pour montrer comment trouver, isoler et nettoyer les responsabilités.
Décider de ce qui appartient à une classe
La partie difficile de la conception au début n'est pas de savoir comment écrire une classe, mais quoi y mettre. Le conseil de Metz : il est plus important d'organiser le code pour qu'il soit facile à modifier que d'obtenir la conception parfaite dès le départ. Visez un code qui soit TRUE :
- Transparent — les conséquences d'un changement sont évidentes.
- Reasonable (raisonnable) — le coût d'un changement est proportionnel à son bénéfice.
- Usable (utilisable) — le code peut être réutilisé dans des contextes nouveaux et inattendus.
- Exemplary (exemplaire) — le code encourage ceux qui le modifient à perpétuer de bonnes habitudes.
Pour y parvenir, on applique le Single Responsibility Principle (SRP) : une classe doit faire la plus petite chose utile possible, et n'avoir qu'une seule raison de changer.
Pourquoi la responsabilité unique est importante
Une classe qui fait trop de choses est difficile à réutiliser — vous ne pouvez pas récupérer uniquement le comportement souhaité sans traîner tout le reste avec. Lorsque vous êtes tenté de copier juste une méthode dans un nouveau contexte, c'est un signe que la classe fait plus d'une chose.
Des responsabilités fortement couplées rendent aussi la classe fragile : un changement destiné à un comportement peut accidentellement en casser un autre, sans rapport.
Comment savoir si une classe a une responsabilité unique
Deux techniques pratiques :
- Interrogez-la comme une phrase. Demandez à la classe ses méthodes et reformulez-les en questions : « Je vous en prie, M. Gear, quel est votre
ratio? » a du sens. « Je vous en prie, M. Gear, quel est votregear_inches? » est à la limite. « Je vous en prie, M. Gear, quelle est votretire_size? » n'a clairement pas de sens — la taille du pneu appartient à une roue, pas à un engrenage. - Décrivez-la en une seule phrase. Si la description la plus simple utilise le mot « et », la classe a probablement plus d'une responsabilité. Si elle utilise « ou », les responsabilités ne sont même pas liées. Une bonne description de classe contient un but unique et cohérent.
Écrire du code qui embrasse le changement
Même avant de connaître la conception finale, vous pouvez écrire du code qui reste flexible.
Dépendre du comportement, pas des données
Cachez les variables d'instance. Ne faites jamais référence directement aux @variables à l'intérieur des méthodes — enveloppez-les dans des méthodes d'accès pour qu'il n'y ait qu'un seul endroit où la donnée est définie :
class Gear
attr_reader :chainring, :cog # creates wrapper methods
def initialize(chainring, cog)
@chainring = chainring
@cog = cog
end
def ratio
chainring / cog.to_f # use the method, not @chainring
end
end
Ainsi, si la signification de cog doit un jour changer, vous modifiez une seule méthode au lieu de traquer chaque référence.
Cachez les structures de données. Si une classe reçoit une structure complexe (disons un tableau de tableaux à deux éléments pour des roues), ne dispersez pas des références par index comme wheel[0] et wheel[1] partout. Chaque référence dépend de la disposition exacte de la structure. À la place, enveloppez-la dans un Struct pour que chaque élément ait un nom :
class ObscuringReferences
attr_reader :data
def initialize(data)
@data = data
end
def diameters
data.collect { |cell| cell[0] + (cell[1] * 2) } # what is [0]? [1]?
end
end
class RevealingReferences
attr_reader :wheels
def initialize(data)
@wheels = wheelify(data)
end
def diameters
wheels.collect { |wheel| wheel.rim + (wheel.tire * 2) } # clear!
end
Wheel = Struct.new(:rim, :tire)
def wheelify(data)
data.collect { |cell| Wheel.new(cell[0], cell[1]) }
end
end
La structure ne vit désormais qu'à un seul endroit.
Appliquer la responsabilité unique partout
La même discipline s'applique en dessous du niveau de la classe :
- Extrayez les responsabilités superflues des méthodes. Les méthodes, comme les classes, doivent faire une seule chose. Des méthodes petites, à but unique, révèlent des qualités cachées de la classe, évitent les commentaires et sont faciles à réutiliser et à déplacer.
- Isolez les responsabilités superflues dans des classes. Lorsqu'une méthode ou une structure embarquée laisse entrevoir un nouveau concept (comme
Wheelvivant à l'intérieur deGear), extrayez-le — même dans un petitStructau début. Vous n'avez pas à vous engager immédiatement dans une nouvelle classe complète ; isolez simplement l'idée pour qu'elle soit prête à être déplacée le moment venu.
Le bénéfice : la future classe Wheel s'écrit pratiquement toute seule, car la responsabilité était déjà proprement isolée.
Points clés à retenir
- Une classe doit avoir une responsabilité unique — une seule raison de changer — ce qui la rend réutilisable et sûre à modifier.
- Testez le SRP en interrogeant les méthodes sous forme de questions et en décrivant la classe en une phrase ; méfiez-vous de « et » et « ou ».
- Visez un code TRUE : Transparent, Reasonable, Usable, Exemplary.
- Cachez les variables d'instance derrière des méthodes d'accès pour que la donnée ait un unique point de définition.
- Cachez les structures de données complexes (par ex. avec
Struct) afin que les références ne dépendent pas de la disposition. - Appliquez aussi la responsabilité unique dans les méthodes — des méthodes petites et ciblées rendent l'extraction et la réutilisation futures peu coûteuses.
- Isolez tôt les responsabilités émergentes, même avant d'être prêt à créer une nouvelle classe complète.
🌐 Traduit par Claude