Comment configurer MVP pour une solution Winforms?

14

J'ai utilisé MVP et MVC dans le passé, et je préfère MVP car il contrôle le flux d'exécution tellement mieux à mon avis.

J'ai créé mon infrastructure (classes de banque de données / référentiel) et les utilise sans problème lors du codage en dur des exemples de données, alors maintenant je passe à l'interface graphique et prépare mon MVP.

Section A

  1. J'ai vu MVP utiliser la vue comme point d'entrée, c'est-à-dire dans la méthode du constructeur de vues, il crée le présentateur, qui à son tour crée le modèle, câblant les événements selon les besoins.

  2. J'ai également vu le présentateur comme le point d'entrée, où une vue, un modèle et un présentateur sont créés, ce présentateur reçoit ensuite une vue et un objet de modèle dans son constructeur pour câbler les événements.

  3. Comme en 2, mais le modèle n'est pas transmis au présentateur. Au lieu de cela, le modèle est une classe statique où les méthodes sont appelées et les réponses retournées directement.

Section B

En termes de synchronisation de la vue et du modèle, j'ai vu.

  1. Chaque fois qu'une valeur dans la vue change, c'est-à-dire un TextChangedévénement en .Net / C #. Cela déclenche un DataChangedEventqui est transmis dans le modèle, pour le garder synchronisé à tout moment. Et lorsque le modèle change, c'est-à-dire un événement d'arrière-plan qu'il écoute, la vue est mise à jour via la même idée de lever a DataChangedEvent. Lorsqu'un utilisateur souhaite valider des modifications, SaveEventil se déclenche, passant dans le modèle pour effectuer la sauvegarde. Dans ce cas, le modèle imite les données de la vue et traite les actions.

  2. Semblable à # b1, cependant, la vue n'est pas toujours synchronisée avec le modèle. Au lieu de cela, lorsque l'utilisateur souhaite valider les modifications, il SaveEventest renvoyé et le présentateur saisit les derniers détails et les transmet au modèle. dans ce cas, le modèle ne connaît pas les données de vues tant qu'il n'est pas nécessaire de les utiliser, auquel cas tous les détails nécessaires lui sont transmis.

Section C

Affichage des objets métier dans la vue, c'est-à-dire un objet (MyClass) et non des données primitives (int, double)

  1. La vue possède des champs de propriété pour toutes ses données qu'elle affichera en tant qu'objets domaine / métier. Tels que view.Animalsexpose une IEnumerable<IAnimal>propriété, même si la vue les traite en nœuds dans un TreeView. Ensuite, pour l'animal sélectionné, il exposerait SelectedAnimalcomme IAnimalpropriété.

  2. La vue n'a aucune connaissance des objets de domaine, elle expose uniquement les propriétés des types d'objets inclus primitifs / framework (.Net / Java). Dans ce cas, le présentateur passera un objet adaptateur l'objet domaine, l'adaptateur traduira ensuite un objet métier donné dans les contrôles visibles sur la vue. Dans ce cas, l'adaptateur doit avoir accès aux commandes réelles de la vue, et pas seulement à n'importe quelle vue, il devient donc plus étroitement couplé.

Section D

Plusieurs vues utilisées pour créer un seul contrôle. c'est-à-dire que vous avez une vue complexe avec un modèle simple comme la sauvegarde d'objets de différents types. Vous pouvez avoir un système de menus sur le côté à chaque clic sur un élément, les commandes appropriées sont affichées.

  1. Vous créez une vue énorme, qui contient tous les contrôles individuels qui sont exposés via l'interface des vues.

  2. Vous avez plusieurs vues. Vous avez une vue pour le menu et un panneau vide. Cette vue crée les autres vues requises mais ne les affiche pas (visible = false), cette vue implémente également l'interface pour chaque vue qu'elle contient (c'est-à-dire les vues enfants) afin qu'elle puisse l'exposer à un présentateur. Le panneau vide est rempli d'autres vues ( Controls.Add(myview)) et ( (myview.visible = true). Les événements déclenchés dans ces vues "enfants" sont gérés par la vue parent qui à son tour transmet l'événement au présentateur, et vice versa pour restituer les événements aux éléments enfants.

  3. Chaque vue, que ce soit le parent principal ou les vues enfant plus petites, est connectée à son propre présentateur et modèle. Vous pouvez littéralement simplement déposer un contrôle de vue dans un formulaire existant et il aura la fonctionnalité prête, il suffit de câbler un présentateur dans les coulisses.

Section E

Si tout a une interface, maintenant basé sur la façon dont le MVP est fait dans les exemples ci-dessus affectera cette réponse car ils pourraient ne pas être compatibles.

  1. Tout a une interface, la vue, le présentateur et le modèle. Chacun d'eux a alors évidemment une mise en œuvre concrète. Même si vous n'avez qu'une seule vue, modèle et présentateur concret.

  2. La vue et le modèle ont une interface. Cela permet aux vues et aux modèles de différer. Le présentateur crée / reçoit des objets de vue et de modèle et il sert simplement à passer des messages entre eux.

  3. Seule la vue a une interface. Le modèle a des méthodes statiques et n'est pas créé, donc pas besoin d'interface. Si vous voulez un modèle différent, le présentateur appelle un ensemble différent de méthodes de classe statique. Étant statique, le modèle n'a aucun lien avec le présentateur.

Pensées personnelles

De toutes les différentes variations que j'ai présentées (la plupart que j'ai probablement utilisées sous une certaine forme), dont je suis sûr qu'il y en a plus. Je préfère A3 comme gardant la logique métier réutilisable en dehors de MVP, B2 pour moins de duplication de données et moins d'événements déclenchés. C1 pour ne pas ajouter dans une autre classe, bien sûr, il met une petite quantité de logique non testable dans une vue (comment un objet de domaine est visualisé) mais cela pourrait être revu par le code, ou simplement visualisé dans l'application. Si la logique était complexe, je serais d'accord avec une classe d'adaptateurs mais pas dans tous les cas. Pour la section D, je pense que D1 crée une vue qui est au moins trop grande pour un exemple de menu. J'ai déjà utilisé D2 et D3. Le problème avec D2 est que vous finissez par avoir à écrire beaucoup de code pour router les événements vers et depuis le présentateur vers la vue enfant correcte, et ce n'est pas compatible par glisser / déposer, chaque nouveau contrôle a besoin de plus de câblage pour supporter le présentateur unique. D3 est mon choix préféré, mais ajoute encore plus de classes en tant que présentateurs et modèles pour gérer la vue, même si la vue s'avère très simple ou n'a pas besoin d'être réutilisée. je pense qu'un mélange de D2 et D3 est mieux basé sur les circonstances. En ce qui concerne la section E, je pense que tout ce qui a une interface pourrait être exagéré. Je le fais déjà pour les objets de domaine / métier et je ne vois souvent aucun avantage dans la "conception" en le faisant, mais cela aide à se moquer des objets dans les tests. Personnellement, je verrais E2 comme une solution classique, bien que j'aie vu E3 utilisé dans 2 projets sur lesquels j'ai travaillé précédemment. je pense qu'un mélange de D2 et D3 est mieux basé sur les circonstances. En ce qui concerne la section E, je pense que tout ce qui a une interface pourrait être exagéré. Je le fais déjà pour les objets de domaine / métier et je ne vois souvent aucun avantage dans la "conception" en le faisant, mais cela aide à se moquer des objets dans les tests. Personnellement, je verrais E2 comme une solution classique, bien que j'aie vu E3 utilisé dans 2 projets sur lesquels j'ai travaillé précédemment. je pense qu'un mélange de D2 et D3 est mieux basé sur les circonstances. En ce qui concerne la section E, je pense que tout ce qui a une interface pourrait être exagéré. Je le fais déjà pour les objets de domaine / métier et je ne vois souvent aucun avantage dans la "conception" en le faisant, mais cela aide à se moquer des objets dans les tests. Personnellement, je verrais E2 comme une solution classique, bien que j'aie vu E3 utilisé dans 2 projets sur lesquels j'ai travaillé précédemment.

Question

Suis-je en train d'implémenter MVP correctement? Y a-t-il une bonne façon de procéder?

J'ai lu le travail de Martin Fowler qui a des variations, et je me souviens quand j'ai commencé à faire du MVC, j'ai compris le concept, mais je ne pouvais pas à l'origine déterminer où est le point d'entrée, tout a sa propre fonction mais ce qui contrôle et crée l'original ensemble d'objets MVC.

JonWillis
la source
2
La raison de poser cette question est que je cherche à bien faire les choses dès la première tentative. Je préférerais avoir un MVP standard à suivre, plutôt que de créer 6 applications en utilisant différentes variations pour le même modèle.
JonWillis
Parfait ... Je voulais le demander depuis longtemps
The King

Réponses:

4

Beaucoup de ce que vous présentez ici est très raisonnable et sain. Certains choix vont dépendre des spécificités de l'application et de celui qui "se sent" bien. Comme c'est le cas la plupart du temps, il n'y aura pas de bonne réponse. Certains choix auront un sens ici et ces choix pourraient être complètement faux pour la prochaine application et les circonstances. Sans connaître certaines des spécificités de l'application, je pense que vous êtes sur la bonne voie et avez pris des décisions judicieuses et réfléchies.

Pour moi, je pense que le présentateur devrait presque toujours être le point d'entrée. Avoir l'interface utilisateur comme point d'entrée met trop de logique dans l'interface utilisateur et enlève la possibilité de remplacer une nouvelle interface utilisateur sans grands changements de codage. Et c'est vraiment le travail du présentateur.

Walter
la source
Peut-être que la question que je devrais poser est l'une des façons d'utiliser MVP tout simplement fausse. Je serais d'accord avec des variations adaptées à différents scénarios, mais je pense qu'il devrait y avoir une approche généralement acceptée. Les exemples de MVP sont tous des exemples simples et presque tous avec le même besoin, pour modifier un objet de domaine et enregistrer les modifications. Pourtant, à partir de ces exemples ayant le même objectif, les variations ci-dessus ont été produites. Je viens de coder une partie d'une application en utilisant des événements déclenchés dans la vue à gérer dans le présentateur, mais j'aurais pu faire en sorte que la vue contienne une référence au présentateur et appelle directement les méthodes.
JonWillis
Je serais d'accord avec tous les efforts pour simplifier la vue pour justifier qu'elle ne soit pas testée à l'unité, alors le présentateur devrait avoir le contrôle. Ma seule préoccupation à ce sujet (penser dans ma tête alors cela pourrait être faux) est que les formes de victoire / contrôles utilisateur peuvent avoir besoin d'être liés aux éléments parents et se demandent si une logique supplémentaire est nécessaire dans un présentateur pour l'écrire.
JonWillis
@Jon - Comment MVP a tort? Dans mon livre, ce serait quand la vue connaît le présentateur. Ce n'est peut-être pas «faux» dans le sens où cela fonctionne, mais ce ne serait tout simplement pas MVP, il est transformé en quelque chose d'autre à ce stade. Le problème avec les modèles de conception est que les exemples sont toujours aussi propres et simples que possible. Ensuite, lorsque vous allez les implémenter pour la première fois et que le monde réel bondit et dit `` les vraies applications sont bien plus compliquées que cet exemple chétif ''. C'est là que votre croissance commence. Trouvez ce qui fonctionne pour vous et les circonstances de l'application.
Walter
Merci pour le conseil. Je me souviens avoir appris le MVC à l'université. Cela sonnait bien, mais avec une toile vierge, vous vous demandez par où commencer et comment cela fonctionne vraiment. En ce qui concerne MVP, je veux dire que l'une des idées / variantes de MVC que j'ai postées ci-dessus était la mauvaise façon de le faire, c'est-à-dire lorsque la vue sait comment fonctionne le présentateur. Ou la vue doit-elle faire référence à une interface de type présentateur ou concret, etc. Juste de nombreuses variantes différentes qui peuvent toutes fonctionner dans le même but.
JonWillis
1
@Jon - Vos variantes sont toutes à peu près conformes à l'esprit de MVP. Pour ce qui est de travailler avec des interfaces ou des types concrets, cela dépendra des circonstances de l'application. Si l'application est assez petite, pas très complexe, il n'est peut-être pas nécessaire d'ajouter des interfaces. J'aime garder les choses aussi simples que possible, jusqu'à ce qu'il soit clair que l'application doit absolument implémenter l'architecture X. Voir cette réponse pour plus de détails: programmers.stackexchange.com/questions/34547/…
Walter
4

Nous utilisons une forme modifiée de MVP sur notre application .NET 2.0 Winforms. Les deux pièces qui nous manquaient étaient un adaptateur modifié du WPF ViewModel et l'ajout d'une liaison de données. Notre modèle spécifique est MVPVM.

Nous câblons en tant que présentateur en premier dans presque tous les cas, à l'exception des commandes d'utilisateur personnalisées, qui sont câblées en vue d'abord pour la convivialité du concepteur. Nous utilisons l'injection de dépendances, les ViewModels générés par code, BDD pour les présentateurs et TDD / TED pour le modèle.

Les machines virtuelles ne sont qu'une masse massive et plate de propriétés qui augmentent PropertyChanged lorsqu'elles sont modifiées. Il était très facile de les générer par code (et les tests unitaires d'exercices associés). Nous les utilisons pour lire et écrire sur des contrôles interactifs et contrôler les états activés. Le ViewModel est couplé à la vue, car nous utilisons la liaison de données pour sacrément près de tout le reste.

La vue aura parfois des méthodes pour accomplir des choses que la machine virtuelle ne peut pas faire. Cela contrôle généralement la visibilité des éléments (WinForms peut être pointilleux à ce sujet) et les choses qui refusent d'être liées aux données. La vue expose toujours des événements comme "Connexion" ou "Redémarrage", avec les EventArgs appropriés pour agir sur les comportements des utilisateurs. À moins que nous n'ayons dû utiliser un hack comme "View.ShowLoginBox", la vue est complètement interchangeable tant qu'elle répond aux exigences générales de conception.

Il nous a fallu environ 6 à 8 mois pour définir ce modèle. Il a beaucoup de pièces, mais est très flexible et extrêmement puissant. Notre implémentation spécifique est très asynchrone et pilotée par les événements, ce qui peut être simplement un artefact d'autres exigences plutôt qu'un effet secondaire du comportement de conception. Par exemple, j'ai ajouté la synchronisation des threads à la classe de base dont nos machines virtuelles héritent (qui a simplement exposé une méthode OnPropertyChanged pour déclencher l'événement) - et pouf, nous pouvons maintenant avoir des présentateurs et des modèles multithreads.

Bryan Boettcher
la source
Je sais que votre MVPVM peut présenter un intérêt commercial, mais si cela vous convient, pourriez-vous fournir un exemple de code? Sur une note latérale, où tracez-vous la ligne sur ce qui fait dans le modèle et ce qui se passe dans le présentateur. J'ai vu le présentateur être si basique qu'il gère les événements d'affichage et appelle simplement le modèle, le présentateur accédant aux couches de données pour transmettre des objets métier à un modèle, jusqu'au modèle remplacé par le présentateur.
JonWillis
Oui, permettez-moi de terminer un exemple. Il sera légèrement adapté à notre activité, car je l'utilise comme outil de formation interne.
Bryan Boettcher
Merci, je vais le regarder le week-end pour voir si je le comprends
JonWillis
@insta - le lien est en panne, pourriez-vous le re-télécharger quelque part?
Yves Schelpe
1
Merde, je l'ai juste supprimé il y a une semaine. Chiffres :(
Bryan Boettcher
2

J'utilise une version de PureMvc qui a été modifiée pour .Net, puis étendue par moi-même.

J'étais habitué à PureMvc de l'utiliser dans des applications Flex. Il s'agit d'un cadre de type barebones, il est donc assez facile de l'adapter si vous souhaitez le personnaliser.

J'ai pris les libertés suivantes avec lui:

  • En utilisant la réflexion, j'ai pu obtenir des propriétés privées dans le médiateur de vues pour aller dans la classe de formulaire et saisir la référence (privée) à chaque contrôle que je médiais.
  • En utilisant la réflexion, je peux câbler automatiquement les contrôles aux signatures d'événements de mon médiateur, qui sont génériques, donc j'active simplement le paramètre expéditeur.
  • Normalement, PureMvc souhaite que les médiateurs dérivés définissent leurs intérêts de notification dans une fonction qui renverra un tableau de chaînes. Étant donné que mes intérêts sont principalement statiques et que je voulais avoir un moyen plus simple de voir les intérêts des médiateurs, j'ai fait une modification pour que le médiateur puisse également déclarer ses intérêts par un ensemble de variables membres avec une signature particulière: _ _ _ <classname> _ <notification-name>. De cette façon, je peux voir ce qui se passe en utilisant l'arbre des membres IDE au lieu de regarder à l'intérieur d'une fonction.

Dans PureMvc, vous pouvez utiliser une commande comme point d'entrée, une commande de démarrage typique configurerait le modèle dans la mesure du possible, puis créerait le MainForm, créerait et enregistrerait le médiateur pour et avec ce formulaire, puis ferait Application. Sur le formulaire.

Le médiateur du formulaire serait responsable de la mise en place de tous les sous-médiateurs, certains pouvant être automatisés en utilisant, encore une fois, des astuces de réflexion.

Le système que j'utilise est compatible par glisser / déposer, si je comprends votre sens. Le formulaire réel est entièrement créé dans VS, mais mon expérience ne concerne que les formulaires dotés de contrôles créés statiquement. Des choses comme des éléments de menu créés dynamiquement semblent réalisables avec un peu de réglage du médiateur pour ce menu ou sous-menu. Là où cela deviendrait poilu, c'est lorsque le médiateur n'avait aucun élément racine statique à accrocher et que vous vous êtes mis à créer des médiateurs «d'instance» dynamiques.

marque
la source