Qu'est-ce qui entre dans le «contrôleur» dans «MVC»?

186

Je pense que je comprends les concepts de base de MVC - le modèle contient les données et le comportement de l'application, la vue est responsable de l'afficher à l'utilisateur et le contrôleur traite les entrées de l'utilisateur. Ce dont je ne suis pas sûr, c'est exactement ce qui se passe dans le contrôleur.

Disons par exemple que j'ai une application assez simple (je pense spécifiquement à Java, mais je suppose que les mêmes principes s'appliquent ailleurs). J'organise mon code en 3 packages appelés app.model, app.viewet app.controller.

Dans le app.modelpackage, j'ai quelques classes qui reflètent le comportement réel de l'application. Ceux extends Observable- ci et utiliser setChanged()et notifyObservers()pour déclencher la mise à jour des vues le cas échéant.

Le app.viewpackage a une classe (ou plusieurs classes pour différents types d'affichage) qui utilise des javax.swingcomposants pour gérer l'affichage. Certains de ces composants doivent être réinjectés dans le modèle. Si je comprends bien, la vue ne devrait rien avoir à voir avec les commentaires - cela devrait être traité par le contrôleur.

Alors, qu'est-ce que je mets réellement dans le contrôleur? Dois-je mettre le public void actionPerformed(ActionEvent e)dans la vue avec juste un appel à une méthode dans le contrôleur? Si tel est le cas, une validation, etc., devrait-elle être effectuée dans le contrôleur? Si tel est le cas, comment puis-je renvoyer les messages d'erreur à la vue - cela doit-il passer à nouveau par le modèle ou le contrôleur doit-il simplement les renvoyer directement à la vue?

Si la validation est effectuée dans la vue, que dois-je mettre dans le contrôleur?

Désolé pour la longue question, je voulais juste documenter ma compréhension du processus et j'espère que quelqu'un pourra clarifier ce problème pour moi!

Paul Walker
la source

Réponses:

532

Dans l'exemple que vous avez suggéré, vous avez raison: "l'utilisateur a cliqué sur le bouton" supprimer cet élément "" dans l'interface devrait simplement appeler la fonction "supprimer" du contrôleur. Le contrôleur, cependant, n'a aucune idée de ce à quoi ressemble la vue, et votre vue doit donc collecter des informations telles que "sur quel élément a été cliqué?"

Sous forme de conversation:

Vue : "Hé, contrôleur, l'utilisateur vient de me dire qu'il veut supprimer l'élément 4."
Contrôleur : "Hmm, après avoir vérifié ses informations d'identification, il est autorisé à le faire ... Hé, modèle, je veux que vous obteniez l'élément 4 et que vous fassiez tout ce que vous faites pour le supprimer."
Modèle : "Item 4 ... je l'ai. Il est supprimé. De retour à vous, contrôleur."
Contrôleur : "Ici, je vais collecter le nouvel ensemble de données. De retour à vous, voir."
Vue : "Cool, je vais montrer le nouvel ensemble à l'utilisateur maintenant."

À la fin de cette section, vous avez une option: soit la vue peut faire une demande séparée, "donnez-moi le jeu de données le plus récent", et donc être plus pure, ou le contrôleur renvoie implicitement le nouvel ensemble de données avec le "supprimer " opération.

Andres Jaan Tack
la source
93
Ce dialogue est la meilleure explication de MVC que j'ai rencontrée, merci!
Paul Walker
13
Tout va bien, mais il n'y a rien de mal à lire la vue directement à partir du modèle. "Les contrôleurs ne sont pas la police des données". Il existe également une doctrine qui dit de garder les contrôleurs minces. Les View Helpers sont un endroit idéal pour collecter des données prêtes à être utilisées par votre vue. On ne devrait pas avoir à envoyer la pile de contrôleurs complète pour réutiliser une logique d'accès aux données. Plus de détails: rmauger.co.uk/2009/03/…
Exception e
1
Je suis d'accord avec "Exception e". Les données du modèle peuvent être mises à jour par de nombreux événements, pas nécessairement le contrôleur, et par conséquent, dans certaines conceptions MVC, le M signale au V que les données sont sales et que le V peut se rafraîchir. Le C n'a aucun rôle à jouer dans ce cas.
Mishax
Le contrôleur ressemble à un service d'application en termes DDD, car dans la boîte de dialogue, il gère un scénario de type Saga, éventuellement à l'intérieur d'une transaction.
Zon
69

Le problème MVCest que les gens pensent que la vue, le contrôleur et le modèle doivent être aussi indépendants que possible l'un de l'autre. Ils ne le voient pas - une vue et un contrôleur sont souvent étroitement liés - le considérer comme M(VC).

Le contrôleur est le mécanisme d'entrée de l'interface utilisateur, qui est souvent emmêlé dans la vue, en particulier avec les interfaces graphiques. Néanmoins, la vue est sortie et le contrôleur est entré. Une vue peut souvent fonctionner sans contrôleur correspondant, mais un contrôleur est généralement beaucoup moins utile sans vue. Les contrôleurs conviviaux utilisent la vue pour interpréter les entrées de l'utilisateur d'une manière plus significative et intuitive. C'est ce qui rend difficile la séparation du concept de contrôleur de la vue.

Pensez à un robot radiocommandé sur un champ de détection dans une boîte scellée comme modèle.

Le modèle est tout au sujet des transitions d'état et d'état sans concept de sortie (affichage) ou de ce qui déclenche les transitions d'état. Je peux obtenir la position du robot sur le terrain et le robot sait comment faire la transition de position (faire un pas en avant / arrière / gauche / droite. Facile à visualiser sans vue ni contrôleur, mais ne fait rien d'utile

Pensez à une vue sans contrôleur, par exemple quelqu'un dans une autre pièce sur le réseau dans une autre pièce regardant la position du robot sous forme de coordonnées (x, y) en continu sur une console de défilement. Cette vue affiche simplement l'état du modèle, mais ce type n'a pas de contrôleur. Encore une fois, il est facile d'envisager cette vue sans contrôleur.

Pensez à un contrôleur sans vue, par exemple quelqu'un enfermé dans un placard avec le contrôleur radio réglé sur la fréquence du robot. Ce contrôleur envoie une entrée et provoque des transitions d'état sans aucune idée de ce qu'il fait au modèle (le cas échéant). Facile à imaginer, mais pas vraiment utile sans une sorte de retour de la vue.

La plupart des interfaces utilisateur conviviales coordonnent la vue avec le contrôleur pour fournir une interface utilisateur plus intuitive. Par exemple, imaginez une vue / contrôleur avec un écran tactile montrant la position actuelle du robot en 2-D et permettant à l'utilisateur de toucher le point sur l'écran qui se trouve juste devant le robot. Le contrôleur a besoin de détails sur la vue, par exemple la position et l'échelle de la fenêtre, et la position des pixels du point touché par rapport à la position des pixels du robot sur l'écran) pour interpréter cela correctement (contrairement au gars enfermé dans le placard avec le contrôleur radio).

Ai-je déjà répondu à votre question? :-)

Le contrôleur est tout ce qui prend l'entrée de l'utilisateur qui est utilisé pour amener le modèle à l'état de transition. Essayez de garder la vue et le contrôleur séparés, mais réalisez qu'ils sont souvent interdépendants l'un de l'autre, donc ce n'est pas grave si la frontière entre eux est floue, c'est-à-dire que la vue et le contrôleur sous forme de packages séparés peuvent ne pas être aussi clairement séparés que vous le feriez comme, mais ça va. Vous devrez peut-être accepter que le contrôleur ne soit pas clairement séparé de la vue car la vue est du modèle.

... une validation, etc. doit-elle être effectuée dans le contrôleur? Si tel est le cas, comment puis-je renvoyer les messages d'erreur à la vue - est-ce que cela doit passer à nouveau par le modèle ou le contrôleur doit-il simplement les renvoyer directement à la vue?

Si la validation est effectuée dans la vue, que dois-je mettre dans le contrôleur?

Je dis qu'une vue et un contrôleur liés doivent interagir librement sans passer par le modèle. Le contrôleur prend les entrées de l'utilisateur et doit effectuer la validation (peut-être en utilisant les informations du modèle et / ou de la vue), mais si la validation échoue, le contrôleur devrait être en mesure de mettre à jour directement sa vue associée (par exemple, message d'erreur).

Le test acide pour cela consiste à se demander si une vue indépendante (c'est-à-dire le gars dans l'autre pièce qui surveille la position du robot via le réseau) devrait voir quelque chose ou non à la suite d'une erreur de validation de quelqu'un d'autre (par exemple, le gars dans le placard a essayé de dire au robot de sortir du terrain). Généralement, la réponse est non - l'erreur de validation a empêché la transition d'état. S'il n'y a pas eu de transition d'état (le robot n'a pas bougé), il n'est pas nécessaire de dire les autres vues. Le gars dans le placard n'a tout simplement pas reçu de commentaires indiquant qu'il avait tenté de provoquer une transition illégale (pas de vue - mauvaise interface utilisateur), et personne d'autre n'a besoin de le savoir.

Si le gars avec l'écran tactile a essayé d'envoyer le robot hors du terrain, il a reçu un joli message convivial lui demandant de ne pas tuer le robot en l'envoyant hors du champ de détection, mais encore une fois, personne d'autre n'a besoin de le savoir.

Si d' autres vues ne devez savoir sur ces erreurs, vous dites effectivement que les entrées de l'utilisateur et les erreurs qui en résultent sont une partie du modèle et l'ensemble est un peu plus compliqué ...

Bert F
la source
23

Voici un bon article sur les bases de MVC.

Il est dit ...

Contrôleur - Le contrôleur traduit les interactions avec la vue en actions à effectuer par le modèle.

En d'autres termes, votre logique métier. Le contrôleur répond aux actions effectuées par l'utilisateur dans la vue et répond. Vous mettez la validation ici et sélectionnez la vue appropriée si la validation échoue ou réussit (page d'erreur, boîte de message, peu importe).

Il y a un autre bon article chez Fowler .

JP Alioto
la source
MVP est une autre option abordée dans l'article auquel
Jon
Merci pour les liens, ils constituent certainement une lecture intéressante.
Paul Walker
20

Le modèle MVC veut simplement que vous sépariez la présentation (= vue) de la logique métier (= modèle). La partie contrôleur n'est là que pour semer la confusion.

Dimitri C.
la source
1
Exactement, ce que j'ai toujours pensé jusqu'à présent, mais je n'ai jamais eu le courage de le dire à personne ... ou peut-être ne pouvais-je pas trouver les mots appropriés.
user1451111
2
Model-View-Confusion
Raining
11

En pratique, je n'ai jamais trouvé le concept de contrôleur particulièrement utile. J'utilise une séparation stricte modèle / vue dans mon code mais il n'y a pas de contrôleur clairement défini. Cela semble être une abstraction inutile.

Personnellement, MVC à part entière semble être le modèle de conception d'usine en ce qu'il conduit facilement à une conception confuse et trop compliquée. Ne soyez pas un astronaute d'architecture .

John Kugelman
la source
9

Sur la base de votre question, j'ai l'impression que vous êtes un peu flou sur le rôle du modèle. Le modèle est fixé sur les données associées à l'application; si l'application a une base de données, le travail du modèle sera de lui parler. Il gérera également toute logique simple associée à ces données; si vous avez une règle qui dit que pour tous les cas où TABLE.foo == "Hourra!" et TABLE.bar == "Huzzah!" puis définissez TABLE.field = "W00t!", puis vous voulez que le modèle s'en occupe.

Le contrôleur est ce qui devrait gérer l'essentiel du comportement de l'application. Alors pour répondre à vos questions:

Dois-je mettre le public void actionPerformed (ActionEvent e) dans la vue avec juste un appel à une méthode dans le contrôleur?

Je dirais non. Je dirais que cela devrait vivre dans le contrôleur; la vue devrait simplement alimenter les données provenant de l'interface utilisateur dans le contrôleur, et laisser le contrôleur décider quelles méthodes doivent être appelées en réponse.

Si tel est le cas, une validation, etc., devrait-elle être effectuée dans le contrôleur?

La majeure partie de votre validation doit vraiment être effectuée par le contrôleur; il doit répondre à la question de savoir si les données sont valides ou non, et si ce n'est pas le cas, transmettre les messages d'erreur appropriés à la vue. Dans la pratique, vous pouvez incorporer quelques vérifications de cohérence simples dans la couche View dans le but d'améliorer l'expérience utilisateur. (Je pense principalement aux environnements Web, où vous voudrez peut-être faire apparaître un message d'erreur au moment où l'utilisateur clique sur "Soumettre" plutôt que d'attendre tout le cycle de soumission -> processus -> chargement de la page avant de leur dire qu'ils ont merdé .) Fais attention; vous ne voulez pas dupliquer les efforts plus que nécessaire, et dans de nombreux environnements (encore une fois, je pense au Web), vous devez souvent traiter toutes les données provenant de l'interface utilisateur comme un paquet de sales ment jusqu'à ce que vous '

Si tel est le cas, comment puis-je renvoyer les messages d'erreur à la vue - est-ce que cela doit passer à nouveau par le modèle ou le contrôleur doit-il simplement les renvoyer directement à la vue?

Vous devriez avoir un protocole configuré où la vue ne sait pas nécessairement ce qui se passe ensuite jusqu'à ce que le contrôleur le dise. Quel écran leur montrez-vous après que l'utilisateur a appuyé sur ce bouton? La vue peut ne pas savoir, et le contrôleur peut ne pas le savoir non plus jusqu'à ce qu'il examine les données qu'il vient d'obtenir. Il peut s'agir de "Accéder à cet autre écran, comme prévu" ou "Rester sur cet écran et afficher ce message d'erreur".

D'après mon expérience, la communication directe entre le modèle et la vue devrait être très, très limitée, et la vue ne devrait pas modifier directement les données du modèle; cela devrait être le travail du contrôleur.

Si la validation est effectuée dans la vue, que dois-je mettre dans le contrôleur?

Voir au dessus; la vraie validation devrait être dans le contrôleur. Et j'espère que vous avez une idée de ce qui devrait être mis dans le contrôleur maintenant. :-)

Il convient de noter que tout cela peut devenir un peu flou sur les bords; comme pour tout ce qui est aussi complexe que le génie logiciel, les appels au jugement abondent. Utilisez simplement votre meilleur jugement, essayez de rester cohérent dans cette application et essayez d'appliquer les leçons que vous apprenez au prochain projet.

BlairHippo
la source
7

Le contrôleur fait vraiment partie de la vue. Son travail consiste à déterminer quel (s) service (s) sont nécessaires pour répondre à la demande, à démarquer les valeurs de la vue dans les objets requis par l'interface de service, à déterminer la vue suivante et à réorganiser la réponse sous une forme que la vue suivante peut utiliser. . Il gère également toutes les exceptions levées et les rend dans des vues que les utilisateurs peuvent comprendre.

La couche de service est la chose qui connaît les cas d'utilisation, les unités de travail et les objets de modèle. Le contrôleur sera différent pour chaque type de vue - vous n'aurez pas le même contrôleur pour les interfaces utilisateur de bureau, basées sur navigateur, Flex ou mobiles. Je dis donc que cela fait vraiment partie de l'interface utilisateur.

Orienté service: c'est là que se fait le travail.

duffymo
la source
4

Voici une règle empirique que j'utilise: s'il s'agit d'une procédure que j'utiliserai spécifiquement pour une action sur cette page, elle appartient au contrôleur, pas au modèle. Le modèle ne doit fournir qu'une abstraction cohérente au stockage des données.

Je suis venu avec cela après avoir travaillé avec une application Web de grande taille écrite par des développeurs qui pensaient qu'ils étaient compris MVC mais ne l'ont pas vraiment fait. Leurs "contrôleurs" sont réduits à huit lignes d'appels de méthodes de classes statiques qui ne sont généralement appelées nulle part ailleurs: - / faire de leurs modèles un peu plus que des moyens de créer des espaces de noms. Le refactoriser correctement fait trois choses: déplace tout le SQL dans la couche d'accès aux données (aka model), rend le code du contrôleur un peu plus détaillé mais beaucoup plus compréhensible, et réduit les anciens fichiers "modèle" à rien. :-)

statique
la source
3

Le contrôleur sert principalement à la coordination entre la vue et le modèle.

Malheureusement, cela finit parfois par être mélangé à la vue - dans de petites applications, bien que ce ne soit pas si mal.

Je vous suggère de mettre le:

public void actionPerformed(ActionEvent e)

dans le contrôleur. Ensuite, votre écouteur d'action dans votre vue doit déléguer au contrôleur.

Quant à la partie validation, vous pouvez la mettre dans la vue ou dans le contrôleur, je pense personnellement qu'elle appartient au contrôleur.

Je recommanderais certainement de jeter un œil à Passive View et Supervising Presenter (qui est essentiellement ce en quoi Model View Presenter est divisé - au moins par Fowler). Voir:

http://www.martinfowler.com/eaaDev/PassiveScreen.html

http://www.martinfowler.com/eaaDev/SupervisingPresenter.html

Jon
la source
1

notez également que chaque widget Swing peut être considéré comme contenant les trois composants MVC: chacun a un modèle (par exemple ButtonModel), une vue (BasicButtonUI) et un contrôle (JButton lui-même).

akf
la source
1

Vous avez essentiellement raison sur ce que vous mettez dans le contrôleur. C'est la seule façon dont le modèle doit interagir avec la vue. L'action effectuée peut être placée dans la vue, mais la fonctionnalité réelle peut être placée dans une autre classe qui agirait en tant que contrôleur. Si vous allez faire cela, je vous recommande de regarder dans le modèle de commande, qui est un moyen d'abstraire toutes les commandes qui ont le même récepteur. Désolé pour la digression.

Quoi qu'il en soit, une implémentation MVC appropriée n'aura que les interactions suivantes: Modèle -> Vue Vue -> Contrôleur Contrôleur -> Vue

Le seul endroit où il peut y avoir une autre interaction est si vous utilisez un observateur pour mettre à jour la vue, alors la vue devra demander au contrôleur les informations dont elle a besoin.

mnuzzo
la source
0

Si je comprends bien, le contrôleur traduit des actions de l'interface utilisateur en actions au niveau de l'application. Par exemple, dans un jeu vidéo, le contrôleur peut traduire "déplacé la souris sur tant de pixels" en "veut regarder dans telle ou telle direction. Dans une application CRUD, la traduction peut être" cliqué sur tel ou tel bouton "en "imprimer cette chose", mais le concept est le même.

David Seiler
la source
0

Nous le faisons ainsi, en utilisant principalement des contrôleurs pour gérer et réagir aux entrées / actions pilotées par l'utilisateur (et _Logic pour tout le reste, sauf la vue, les données et les éléments évidents de _Model):

(1) (réponse, réaction - ce que "fait" l'application Web en réponse à l'utilisateur) Blog_Controller

-> principal ()

-> handleSubmit_AddNewCustomer ()

-> verifyUser_HasProperAuth ()

(2) (logique "métier", quoi et comment la webapp "pense") Blog_Logic

-> sanityCheck_AddNewCustomer ()

-> handleUsernameChange ()

-> sendEmail_NotifyRequestedUpdate ()

(3) (vues, portails, comment la webapp "apparaît") Blog_View

-> genWelcome ()

-> genForm_AddNewBlogEntry ()

-> genPage_DataEntryForm ()

(4) (objet de données uniquement, acquis dans _ construct () de chaque classe Blog *, utilisé pour conserver toutes les données webapp / inmemory ensemble en un seul objet) Blog_Meta

(5) (couche de données de base, lit / écrit dans les bases de données) Blog_Model

-> saveDataToMemcache ()

-> saveDataToMongo ()

-> saveDataToSql ()

-> loadData ()

Parfois, nous ne savons pas où placer une méthode, dans le C ou le L. Mais le modèle est solide comme le roc, limpide, et comme toutes les données en mémoire résident dans le _Meta, c'est aussi une évidence. . Au fait, notre plus grand bond en avant a été d'adopter l'utilisation de _Meta, car cela éliminait toutes les impuretés des différents objets _C, _L et _Model, facilitait la gestion mentale de tout cela, et en un seul coup, cela nous donnait ce qui était appelé "Dependency Injection", ou un moyen de faire circuler un environnement entier avec toutes les données (dont le bonus est la création facile d'un environnement "test").

FYA
la source