Par exemple, supposons que vous ayez un programme de jeu sur console, qui a toutes sortes de méthodes d'entrée / sortie vers et depuis la console. Serait - il intelligent de les garder tous en une seule inputOutput
classe ou les décomposer à des classes plus spécifiques comme de startMenuIO
, inGameIO
, playerIO
, gameBoardIO
, etc. de telle sorte que chaque classe a environ 1-5 méthodes?
Et sur la même note, s'il est préférable de les décomposer, serait-il intelligent de les placer dans un IO
espace de noms rendant ainsi les appeler un peu plus verbeux, par exemple: IO.inGame
etc.?
Réponses:
Mise à jour (récapitulation)
Depuis que j'ai écrit une réponse assez verbeuse, voici ce que cela se résume à:
inGameIO
et lesplayerIO
classes constitueraient probablement une violation du SRP. Cela signifie probablement que vous associez la façon dont vous gérez les E / S avec la logique d'application.Je pense que vous regardez cela dans le mauvais sens. Vous séparez l'IO en fonction des composants de l'application, alors que - pour moi - il est plus logique d'avoir des classes d'E / S distinctes en fonction de la source et du "type" d'E / S.
Avoir des
KeyboardIO
classes de base / génériquesMouseIO
pour commencer, puis en fonction du moment et de l'endroit où vous en avez besoin, ont des sous-classes qui gèrent différemment les E / S.Par exemple, la saisie de texte est quelque chose que vous voudrez probablement gérer différemment des contrôles en jeu. Vous vous retrouverez à vouloir mapper certaines clés différemment selon chaque cas d'utilisation, mais ce mappage ne fait pas partie de l'IO lui-même, c'est la façon dont vous gérez l'IO.
S'en tenir au SRP, j'aurais quelques classes que je peux utiliser pour les entrées-sorties clavier. Selon la situation, je souhaiterai probablement interagir différemment avec ces classes, mais leur seul travail est de me dire ce que fait l'utilisateur.
J'injecterais ensuite ces objets dans un objet gestionnaire qui mapperait le IO brut sur quelque chose avec lequel ma logique d'application peut fonctionner (par exemple: l'utilisateur appuie sur "w" , le gestionnaire le mappe
MOVE_FORWARD
).Ces gestionnaires, à leur tour, sont utilisés pour faire bouger les personnages et dessiner l'écran en conséquence. Une simplification grossière, mais l'essentiel est ce genre de structure:
Ce que nous avons maintenant, c'est une classe qui est responsable de l'IO du clavier dans sa forme brute. Une autre classe qui traduit ces données en quelque chose que le moteur de jeu peut réellement donner un sens, ces données sont ensuite utilisées pour mettre à jour l'état de tous les composants impliqués, et enfin, une classe distincte se chargera de la sortie à l'écran.
Chaque classe a un seul travail: la gestion des entrées au clavier est effectuée par une classe qui ne sait pas / ne prend pas soin / doit savoir ce que signifie l'entrée qu'elle traite. Tout ce qu'il fait, c'est savoir comment obtenir l'entrée (tamponnée, non tamponnée, ...).
Le gestionnaire traduit cela en une représentation interne pour le reste de l'application pour donner un sens à ces informations.
Le moteur de jeu prend les données qui ont été traduites et les utilise pour informer tous les composants pertinents que quelque chose se passe. Chacun de ces composants ne fait qu'une chose, qu'il s'agisse de contrôles de collision ou de changements d'animation de personnage, peu importe, c'est à chaque objet individuel.
Ces objets retransmettent ensuite leur état et ces données sont transmises à
Game.Screen
, qui est essentiellement un gestionnaire d'E / S inversé. Il mappe la représentation interne sur quelque chose que leIO.Screen
composant peut utiliser pour générer la sortie réelle.la source
IO
etgame
les classes sont-ils avec des sous-classes?Le principe de la responsabilité unique peut être difficile à comprendre. Ce que j'ai trouvé utile, c'est de penser à la façon dont vous écrivez des phrases. Vous n'essayez pas de rassembler beaucoup d'idées en une seule phrase. Chaque phrase doit énoncer clairement une idée et différer les détails. Par exemple, si vous vouliez définir une voiture, vous diriez:
Ensuite, vous définiriez séparément des éléments comme "véhicule", "route", "roues", etc. Vous n'essayez pas de dire:
De même, vous devriez essayer de faire en sorte que vos classes, méthodes, etc. énoncent le concept central aussi simplement que possible et reportent les détails à d'autres méthodes et classes. Tout comme pour l'écriture de phrases, il n'y a pas de règle stricte quant à leur taille.
la source
Je dirais que la meilleure façon de procéder est de les garder dans des classes séparées. Les petites classes ne sont pas mauvaises, en fait la plupart du temps c'est une bonne idée.
En ce qui concerne votre cas spécifique, je pense qu'avoir les éléments séparés peut vous aider à changer la logique de l'un de ces gestionnaires spécifiques sans affecter les autres et, si nécessaire, il serait plus facile pour vous d'ajouter une nouvelle méthode d'entrée / sortie si elle venait à se produire.
la source
Le directeur à responsabilité unique déclare qu'une classe ne devrait avoir qu'une seule raison de changer. Si votre classe a plusieurs raisons de changer, vous pouvez la diviser en d'autres classes et utiliser la composition pour éliminer ce problème.
Pour répondre à votre question, je dois vous poser une question: votre classe a-t-elle une seule raison de changer? Sinon, n'ayez pas peur de continuer à ajouter des classes plus spécialisées jusqu'à ce que chacune n'ait qu'une seule raison de changer.
la source