MVC: le contrôleur enfreint-il le principe de responsabilité unique?

16

Le principe de responsabilité unique stipule qu '"une classe devrait avoir une raison de changer".

Dans le modèle MVC, le travail du contrôleur consiste à assurer la médiation entre la vue et le modèle. Il offre une interface pour que la vue signale les actions effectuées par l'utilisateur sur l'interface graphique (par exemple, permettant à la vue d'appeler controller.specificButtonPressed()), et est capable d'appeler les méthodes appropriées sur le modèle afin de manipuler ses données ou d'appeler ses opérations (par exemple model.doSomething()) .

Cela signifie que:

  • Le contrôleur doit connaître l'interface graphique, afin d'offrir à la vue une interface appropriée pour signaler les actions de l'utilisateur.
  • Il doit également connaître la logique du modèle, afin de pouvoir invoquer les méthodes appropriées sur le modèle.

Cela signifie que cela a deux raisons de changer : un changement dans l'interface graphique et un changement dans la logique commerciale.

Si l'interface graphique change, par exemple un nouveau bouton est ajouté, le contrôleur peut avoir besoin d'ajouter une nouvelle méthode pour permettre à la vue de signaler qu'un utilisateur appuie sur ce bouton.

Et si la logique métier du modèle change, le contrôleur devra peut-être changer pour appeler les bonnes méthodes sur le modèle.

Par conséquent, le contrôleur a deux raisons possibles de changer . Est-ce que cela brise le SRP?

Aviv Cohn
la source
2
Un contrôleur est une rue à double sens, ce n'est pas votre approche descendante ou ascendante typique. Il ne lui est pas possible d'abstraire une de ses dépendances car le contrôleur est l'abstraction elle-même. En raison de la nature du modèle, il n'est pas possible d'adhérer au SRP ici. En bref: oui, il viole SRP mais c'est inévitable.
Jeroen Vannevel
1
Quel est le point de la question? Si nous répondons tous "oui, c'est le cas", alors quoi? Et si la réponse est "non"? Quel est le vrai problème que vous essayez de résoudre avec cette question?
Bryan Oakley
1
"une raison de changer" ne signifie pas "un code qui change". Si vous faites sept fautes de frappe dans vos noms de variables pour la classe, cette classe a-t-elle maintenant 7 responsabilités? Non. Si vous avez plusieurs variables ou plusieurs fonctions, vous pouvez également n'avoir qu'une seule responsabilité.
Bob

Réponses:

14

Si vous continuez par conséquent à raisonner sur le PÉR, vous remarquerez que la «responsabilité unique» est en fait un terme spongieux. Notre cerveau humain est en quelque sorte capable de faire la distinction entre différentes responsabilités et plusieurs responsabilités peuvent être résumées en une seule responsabilité «générale». Par exemple, imaginez que dans une famille de 4 personnes, il y ait un membre de la famille responsable de préparer le petit-déjeuner. Maintenant, pour ce faire, il faut faire bouillir des œufs et du pain grillé et bien sûr mettre en place une bonne tasse de thé vert (oui, le thé vert est le meilleur). De cette façon, vous pouvez diviser "faire le petit déjeuner" en petits morceaux qui sont ensemble abstraits de "faire le petit déjeuner". Notez que chaque pièce est également une responsabilité qui pourrait par exemple être déléguée à une autre personne.

Revenons au MVC: si la médiation entre le modèle et la vue n'est pas une responsabilité mais deux, alors quelle serait la prochaine couche d'abstraction ci-dessus, combinant ces deux? Si vous ne pouvez pas en trouver un, vous ne l'avez pas résumé correctement ou il n'y en a pas, ce qui signifie que vous avez tout compris. Et je pense que c'est le cas avec un contrôleur, manipulant une vue et un modèle.

valenterry
la source
1
Tout comme une note ajoutée. Si vous avez controller.makeBreakfast () et controller.wakeUpFamily (), alors ce contrôleur briserait SRP, mais pas parce que c'est un contrôleur, juste parce qu'il a plus d'une responsabilité.
Bob
Merci d'avoir répondu, je ne suis pas sûr de vous suivre. Êtes-vous d'accord que le responsable du traitement a plus d'une responsabilité? La raison pour laquelle je pense que c'est parce qu'il a deux raisons de changer (je pense): un changement dans le modèle et un changement dans la vue. Es-tu d'accord avec ça?
Aviv Cohn
1
Oui, je peux convenir que le contrôleur a plus d'une responsabilité. Cependant, ce sont des responsabilités "inférieures" et ce n'est pas un problème car au niveau d'abstraction le plus élevé, il n'a qu'une seule responsabilité (en combinant les plus faibles que vous mentionnez) et donc il ne viole pas le SRP.
valenterry
1
Trouver le bon niveau d'abstraction est certainement important. L'exemple de «préparer le petit-déjeuner» est bon - pour remplir une seule responsabilité, il y a souvent un certain nombre de tâches à accomplir. Tant que le contrôleur orchestre uniquement ces tâches, il suit SRP. Mais s'il en sait trop sur la cuisson des œufs, la fabrication de pain grillé ou la préparation du thé, cela violerait le SRP.
Allan
Cette réponse est logique pour moi. Merci Valenterry.
J86
9

Si une classe a "deux raisons possibles de changer", alors oui, elle viole SRP.

Un contrôleur doit généralement être léger et avoir la seule responsabilité de manipuler le domaine / modèle en réponse à un événement piloté par l'interface graphique. Nous pouvons considérer chacune de ces manipulations comme des cas d'utilisation ou des fonctionnalités.

Si un nouveau bouton est ajouté sur l'interface graphique, le contrôleur ne devrait avoir à changer que si ce nouveau bouton représente une nouvelle fonctionnalité (c'est-à-dire contrairement au même bouton qui existait à l'écran 1 mais n'existait pas encore à l'écran 2, et c'est alors ajouté à l'écran 2). Il faudrait également un nouveau changement correspondant dans le modèle pour prendre en charge cette nouvelle fonctionnalité / caractéristique. Le contrôleur a toujours la responsabilité de manipuler le domaine / modèle en réponse à un événement piloté par l'interface graphique.

Si la logique métier du modèle change en raison d'un bug corrigé et que le contrôleur doit changer, alors c'est un cas spécial (ou peut-être que le modèle viole le principal ouvert-fermé). Si la logique métier du modèle change pour prendre en charge de nouvelles fonctionnalités / fonctionnalités, cela n'impacte pas nécessairement le contrôleur - uniquement si le contrôleur a besoin d'exposer cette fonctionnalité (ce qui serait presque toujours le cas, sinon pourquoi serait-il ajouté à le modèle de domaine s'il ne sera pas utilisé). Donc, dans ce cas, le contrôleur doit également être modifié pour prendre en charge la manipulation du modèle de domaine de cette nouvelle manière en réponse à un événement piloté par l'interface graphique.

Si le contrôleur doit changer parce que, disons, la couche de persistance est passée d'un fichier plat à une base de données, alors le contrôleur viole certainement SRP. Si le contrôleur fonctionne toujours sur la même couche d'abstraction, cela peut aider à atteindre SRP.

Jordan
la source
4

Le contrôleur ne viole pas SRP. Comme vous le dites, sa responsabilité est de faire la médiation entre les modèles et la vue.

Cela étant dit, le problème avec votre exemple est que vous liez les méthodes du contrôleur à la logique dans la vue, c'est-à-dire controller.specificButtonPressed. Nommer les méthodes de cette manière lie le contrôleur à votre interface graphique, vous n'avez pas réussi à abstraire correctement les choses. Le contrôleur doit être sur l'exécution d'actions spécifiques, c'est controller.saveData-à- dire ou controller.retrieveEntry. L'ajout d'un nouveau bouton dans l'interface graphique ne signifie pas nécessairement l'ajout d'une nouvelle méthode au contrôleur.

En appuyant sur un bouton dans la vue, cela signifie faire quelque chose, mais quoi que ce soit aurait pu facilement être déclenché de plusieurs façons ou même pas à travers la vue.

Extrait de l'article Wikipedia sur SRP

Martin définit une responsabilité comme une raison de changer et conclut qu'une classe ou un module devrait avoir une et une seule raison de changer. Par exemple, considérons un module qui compile et imprime un rapport. Un tel module peut être modifié pour deux raisons. Premièrement, le contenu du rapport peut changer. Deuxièmement, le format du rapport peut changer. Ces deux choses changent pour des causes très différentes; un substantif et un cosmétique. Le principe de la responsabilité unique dit que ces deux aspects du problème sont en réalité deux responsabilités distinctes et devraient donc être dans des classes ou des modules distincts. Ce serait une mauvaise conception de coupler deux choses qui changent pour des raisons différentes à des moments différents.

Le contrôleur ne se préoccupe pas uniquement de ce qui se trouve dans la vue, mais lorsque l'une de ses méthodes est appelée, il fournit des données spécifiées à la vue. Il n'a besoin de connaître les fonctionnalités du modèle que dans la mesure où il sait qu'il doit appeler les méthodes dont il dispose. Il ne sait rien de plus que cela.

Savoir qu'un objet a une méthode disponible pour appeler n'est pas la même chose que connaître sa fonctionnalité.

Schleis
la source
1
La raison pour laquelle je pensais que le contrôleur devrait inclure des méthodes comme specificButtonsPressed()c'est parce que j'ai lu que la vue ne devrait rien savoir sur la fonctionnalité de ses boutons et autres éléments de l'interface graphique. On m'a appris que lorsqu'un bouton est enfoncé, la vue doit simplement faire rapport au contrôleur, et le contrôleur doit décider «ce que cela signifie» (puis invoquer les méthodes appropriées sur le modèle). Faire l'appel de vue controller.saveData()signifie que la vue doit savoir ce que signifie cette pression sur un bouton, en plus du fait qu'elle a été pressée.
Aviv Cohn
1
Si j'abandonne l'idée d'une séparation complète entre une pression sur un bouton et sa signification (ce qui fait que le contrôleur a des méthodes comme specificButtonPressed()), en effet, le contrôleur ne serait pas tellement lié à l'interface graphique. Dois-je abandonner les specificButtonPressed()méthodes? Est-ce que l'avantage que je pense avoir ces méthodes a du sens pour vous? Ou est-ce que le fait d'avoir des buttonPressed()méthodes dans le contrôleur ne vaut pas la peine?
Aviv Cohn
1
En d'autres termes (désolé pour les longs commentaires): Je pense que l'avantage d'avoir des specificButtonPressed()méthodes dans le contrôleur, c'est qu'il sépare complètement la vue de la signification de ses boutons . Cependant, l'inconvénient est qu'il lie le contrôleur à l'interface graphique dans un sens. Quelle approche est la meilleure?
Aviv Cohn
@prog IMO Le contrôleur doit être aveugle à la vue. Un bouton aura une sorte de fonctionnalité mais il n'a pas besoin d'en connaître les détails. Il a juste besoin de savoir qu'il envoie des données à une méthode de contrôleur. À cet égard, le nom n'a pas d'importance. Il peut être appelé foo, ou tout aussi facilement fireZeMissiles. Il saura seulement qu'il doit se rapporter à une fonction spécifique. Il ne sait pas ce que fait la fonction qui l'appellera. Le contrôleur ne se soucie pas de la façon dont ses méthodes sont invoquées, juste qu'il répondra d'une certaine manière quand elles le seront.
Schleis
1

La responsabilité unique d'un contrôleur est d'être le contrat qui sert de médiateur entre la vue et le modèle. La vue ne doit être responsable que de l'affichage, le modèle ne doit être responsable que de la logique métier. C'est la responsabilité des contrôleurs de relier ces deux responsabilités.

C'est bien beau, mais s'aventurer un peu loin du milieu universitaire; un contrôleur dans MVC est généralement composé de nombreuses méthodes d'action plus petites. Ces actions correspondent généralement à des choses qu'une chose peut faire. Si je vends des produits, j'aurai probablement un ProductController. Ce contrôleur aura des actions comme GetReviews, ShowSpecs, AddToCart ect ...

La vue a le SRP d'afficher l'interface utilisateur, et une partie de cette interface utilisateur comprend un bouton qui dit AddToCart.

Le contrôleur a le SRP de connaître toutes les vues et modèles impliqués dans le processus.

L'action AddToCart des contrôleurs a le SRP spécifique de connaître toutes les personnes qui doivent être impliquées lorsqu'un article est ajouté à un panier.

Le modèle de produit a le SRP de modélisation de la logique du produit, et le modèle de ShoppingCart a le SRP de modélisation de la façon dont les articles sont enregistrés pour une extraction ultérieure. Le modèle utilisateur a un SRP de modélisation de l'utilisateur qui ajoute des éléments à son panier.

Vous pouvez et devez réutiliser des modèles pour faire fonctionner votre entreprise et ces modèles doivent être couplés à un moment donné dans votre code. Le contrôleur contrôle chaque façon unique dont le couplage se produit.

WhiteleyJ
la source
0

Les contrôleurs n'ont en fait qu'une seule responsabilité: modifier l'état des applications en fonction de l'entrée de l'utilisateur.

Un contrôleur peut envoyer des commandes au modèle pour mettre à jour l'état du modèle (par exemple, éditer un document). Il peut également envoyer des commandes à sa vue associée pour modifier la présentation de la vue du modèle (par exemple, en faisant défiler un document).

source: wikipedia

Si vous avez plutôt des "contrôleurs" de style Rails (qui jonglent avec des instances d'enregistrement actives et des modèles stupides) , alors bien sûr, vous brisez SRP.

Là encore, les applications de style Rails ne sont pas vraiment MVC pour commencer.

mefisto
la source