Réflexions sur la mise en oeuvre de Model-View-Presenter

34

J'essaie de bien comprendre comment implémenter un bon découplage entre une interface utilisateur et le modèle, mais je ne parviens pas à déterminer exactement où diviser les lignes.

J'ai examiné Model-View-Presenter, mais je ne sais pas exactement comment procéder pour le mettre en œuvre. Par exemple, ma vue comporte plusieurs boîtes de dialogue.

  • Devrait-il y avoir une classe View avec des instances de chacun des dialogues? Dans ce cas, comment les dialogues devraient-ils interagir avec le présentateur? c'est à dire. Si un dialogue individuel doit demander des données au modèle via le présentateur, comment le dialogue doit-il obtenir une référence au présentateur? Via une référence à la vue donnée lors de la construction?
  • Je pensais que peut-être la vue devrait être une classe statique? Ensuite, les dialogues GetView et obtenir le présentateur à partir de là ...
  • J'avais pensé à configurer le présentateur avec la propriété de la vue et du modèle (par opposition à la vue ayant le présentateur et le présentateur ayant le modèle) et le présentateur enregistrant des rappels pour les événements dans la vue, mais cela semble beaucoup plus couplé (ou langue dépendante, au moins.)

J'essaye de:

  1. faire ceci aussi découplé que possible
  2. idéalement, il est possible de coupler le présentateur / modèle avec des vues d'autres langues (je n'ai pas fait beaucoup de choses inter-langues, mais je sais que c'est possible, en particulier si void(void)je peux en tenir plus, au moins une application C # avec un Bibliothèque C ++ ...
  3. garder le code propre et simple

Alors .. des suggestions sur la façon dont les interactions devraient être gérées?

trycatch
la source
Avez-vous consulté cet article ?: fr.wikipedia.org/wiki/Model-view-presenter
Bernard
1
Je l'ai .. Je l'ai trouvé un peu rapide et de haut niveau, je cherche à mieux comprendre comment gérer plusieurs dialogues dans un grand projet avec le moins de couplage possible ..
trycatch

Réponses:

37

Bienvenue sur une pente glissante. À ce stade, vous avez compris qu’il existait une variation infinie de toutes les interactions modèle-vue. MVC, MVP (Taligent, Dolphin, Passive View), MVVM, pour n'en nommer que quelques-uns.

Le modèle Présentateur de vue modèle, comme la plupart des modèles architecturaux, est ouvert à beaucoup de variété et d'expérimentation. Toutes les variations ont en commun le rôle du présentateur en tant qu '"intermédiaire" entre la vue et le modèle. Les deux plus courantes sont la vue passive et le présentateur / contrôleur superviseur - [ Fowler ]. La vue passive considère l'interface utilisateur comme une interface très peu profonde entre l'utilisateur et le présentateur. Il contient très peu de logique, voire aucune, déléguant autant de responsabilités à un présentateur. Superviseur Présentateur / Contrôleurtente de tirer parti de la liaison de données intégrée à de nombreux cadres d’interface utilisateur. L'interface utilisateur gère la synchronisation des données, mais l'animateur / contrôleur intervient pour une logique plus complexe. Dans les deux cas, le modèle, la vue et le présentateur forment une triade

Il y a plusieurs façons de le faire. Il est très courant de voir cela traité en traitant chaque boîte de dialogue / formulaire comme une vue différente. Plusieurs fois, il existe une relation 1: 1 entre les points de vue et les présentateurs. Ce n'est pas une règle difficile et rapide. Il est assez courant qu'un seul présentateur gère plusieurs vues associées, ou inversement. Tout dépend de la complexité de la vue et de la complexité de la logique métier.

Quant à la façon dont les vues et les présentateurs obtiennent une référence les uns aux autres, on parle parfois de câblage . Vous avez trois choix:

La vue contient une référence au présentateur
Un formulaire ou une boîte de dialogue implémente une vue. Le formulaire comporte des gestionnaires d'événements qui se connectent à un présentateur à l'aide d'appels de fonction directs:

MyForm.SomeEvent(Sender)
{
  Presenter.DoSomething(Sender.Data);
}

Comme le présentateur n'a pas de référence à la vue, celle-ci doit lui envoyer des données en tant qu'arguments. Le présentateur peut communiquer avec la vue à l'aide des fonctions d'événements / de rappel que la vue doit écouter.

Presenter contient une référence à afficher
Dans le scénario, la vue expose les propriétés des données affichées à l'utilisateur. Le présentateur écoute les événements et manipule les propriétés de la vue:

Presenter.SomeEvent(Sender)
{
  DomainObject.DoSomething(View.SomeProperty);
  View.SomeOtherProperty = DomainObject.SomeData;
}

Les deux font référence les uns aux autres, formant une dépendance circulaire.
Ce scénario est en réalité plus facile à travailler que les autres. La vue répond aux événements en appelant des méthodes dans le présentateur. Le présentateur lit / modifie les données de la vue via les propriétés exposées.

View.SomeEvent(Sender)
{
  Presenter.DoSomething();
}

Presenter.DoSomething()
{
  View.SomeProperty = DomainObject.Calc(View.SomeProperty);
}

Il y a d'autres problèmes à prendre en compte avec les modèles MVP. Ordre de création, durée de vie de l'objet, lieu de câblage, communication entre les triades MVP, mais cette réponse a déjà suffisamment évolué.

Kenneth Cochran
la source
1
C'est vraiment utile. La communication entre les triades et la vie réelle est l’endroit où j’ai du mal à saisir ces difficultés.
Trycatch
8

Comme tout le monde l’a dit, il existe des dizaines d’opinions et aucune d’entre elles n’est bonne ou mauvaise. Sans entrer dans la myriade de modèles et se concentrer uniquement sur MVP, voici quelques suggestions sur la mise en œuvre.

Gardez-les séparés. La vue doit implémenter une interface qui forme le lien entre la vue et le présentateur. La vue crée un présentateur et s’injecte dans le présentateur et expose les méthodes qu’il offre pour que le présentateur puisse interagir avec la vue. La vue est responsable de l’implémentation de ces méthodes ou propriétés à sa guise. En général, vous avez un seul point de vue: un présentateur, mais dans certains cas, vous pouvez en avoir plusieurs: un seul (Web, wpf, etc.). La clé ici est que le présentateur ne sait rien des implémentations de l'interface utilisateur et n'interagit avec la vue que via l'interface.

Voici un exemple. Nous avons d'abord une classe de vue avec une méthode simple pour afficher un message à l'utilisateur:

interface IView
{
  public void InformUser(string message);
}

Maintenant, voici le présentateur. Notez que le présentateur prend un IView dans son constructeur.

class Presenter
{
  private IView _view;
  public Presenter(IView view)
  {
    _view = view;
  }
}

Maintenant, voici l'interface utilisateur réelle. Cela peut être une fenêtre, une boîte de dialogue, une page Web, etc. Peu importe. Notez que le constructeur de la vue créera le présentateur en s'y injectant.

class View : IView
{
  private Presenter _presenter;

  public View()
  {
    _presenter = new Presenter(this);
  }

  public void InformUser(string message)
  {
    MessageBox.Show(message);
  }
}

Le présentateur se fiche de la façon dont la vue implémente la méthode qu’elle utilise. Pour tous les présentateurs, il s’agit peut-être d’écrire dans un fichier journal et de ne pas le montrer à l’utilisateur.

Dans tous les cas, le présentateur travaille sur le modèle à l'arrière-plan et souhaite informer l'utilisateur de ce qui se passe. Nous avons donc maintenant une méthode quelque part dans le présentateur qui appelle les vues InformUser.

class Presenter
{
  public void DoSomething()
  {
    _view.InformUser("Starting model processing...");
  }
}

C'est là que vous obtenez votre découplage. Le présentateur ne fait référence qu'à une implémentation d'IView et ne se soucie pas vraiment de la façon dont il est implémenté.

Ceci est également une mauvaise implémentation pour l'homme car vous avez une référence au présentateur dans la vue et les objets sont définis via des constructeurs. Dans une solution plus robuste, vous voudrez probablement examiner les conteneurs d'inversion de contrôle (IoC) tels que Windsor, Ninject, etc.

Bil Simser
la source
4

Je pense qu'il est important de se rappeler que l'action se déroule réellement dans le contrôleur / présentateur. Le couplage dans le contrôleur est inévitable par nécessité.

Le point essentiel du contrôleur est que, si vous modifiez la vue, le modèle n’a pas à changer et inversement (si le modèle change, la vue ne doit pas non plus l'être) car le contrôleur traduit les Modèle dans la vue et à nouveau. Mais le contrôleur changera lorsque les modifications de modèle ou de vue le seront, car vous devrez effectivement traduire dans le contrôleur la manière dont le modèle est visualisé pour que les modifications apportées à la vue reviennent dans le mode.

Le meilleur exemple que je puisse donner est que, lorsque j'écris une application MVC, je peux non seulement avoir des données dans la vue GUI, mais aussi écrire une routine qui insère les données extraites du modèle dans un stringdébogage. (et par extension dans un fichier texte brut). Si je peux prendre les données de modèle et les traduire librement en texte sans changer la vue ou le modèle et uniquement le contrôleur, alors je suis sur le bon chemin.

Cela étant dit, vous devrez avoir des références entre les différents composants pour que tout fonctionne. Le contrôleur doit connaître la vue pour transmettre des données, la vue doit savoir que le contrôleur lui indique lorsqu'un changement a été effectué (par exemple, lorsque l'utilisateur clique sur "Enregistrer" ou sur "Nouveau ..."). Le contrôleur doit connaître le modèle pour extraire les données, mais je dirais que le modèle ne devrait rien savoir d’autre.

Mise en garde: Je viens d'un milieu totalement Mac, Objective-C, Cocoa qui vous propulse vraiment dans le paradigme du MVC, que vous le vouliez ou non.

Philip Regan
la source
C'est définitivement mon objectif. Mon problème principal est de savoir comment configurer la vue - que ce soit une classe avec une instance de chaque boîte de dialogue, puis utiliser View.Getters qui appelle Dialog.Getters ou si le présentateur doit pouvoir appeler Dialog.Getters directement ( cela semble trop étroitement couplé, alors probablement pas?)
trycatch
Je pense que le présentateur / contrôleur devrait être entièrement responsable des vues, donc de ces dernières. Encore une fois, un certain couplage est inévitable, mais au moins si la direction à prendre est claire, la maintenance devrait être plus facile à long terme.
Philip Regan
2
Je suis certainement d'accord avec le P / C qui devrait être responsable de la vue, mais je pensais qu'une partie de ce qui était censé rendre MVP puissant était la capacité d'extraire toute la bibliothèque d'interface utilisateur et d'en brancher une nouvelle avec un peu de massage (dllimporting et autres) être capable de courir un autre à sa place. Cela ne serait-il pas plus difficile si le contrôleur / présentateur accède directement aux dialogues? Je ne cherche certainement pas à discuter, mais à mieux comprendre :)
trycatch Le
Je pense que le vrai pouvoir vient de deux directions: la première est que la vue et le modèle n’ont rien à voir avec une autre, et la seconde que la majorité du travail de développement, le moteur de l’application, se fait de manière parfaitement contenue. unité, le contrôleur. Mais il est inévitable que des pertes de responsabilité surviennent. Au moins la majorité des échanges d'interfaces se fera dans le contrôleur et toute liaison à partir de la vue sera minimale. Comme d’autres l’ont dit, certains saignements de logique sont attendus et autorisés. MVC n'est pas une solution miracle.
Philip Regan
Le point important pour le découplage est que le présentateur accède UNIQUEMENT à la vue par l’intermédiaire d’interfaces bien définies (indépendantes de la bibliothèque d’UI), ce qui permet de remplacer la bibliothèque d’UI par une autre (une autre qui implémentera la même interface pour formulaire / fenêtre / dialogue / page / contrôle / peu importe)
Marcel Toth
2

En général, vous souhaitez que votre modèle encapsule toutes les interactions avec ce modèle. Par exemple, vos actions CRUD (Créer, Lire, Mettre à jour, Supprimer) font toutes partie du modèle. Il en va de même pour les calculs spéciaux. Il y a deux bonnes raisons à cela:

  • Il est plus facile d'automatiser vos tests pour ce code
  • Il garde toutes ces choses importantes au même endroit

Dans votre contrôleur (application MVC), vous ne collectez que les modèles à utiliser dans votre vue et appelez les fonctions appropriées du modèle. Toute modification de l'état du modèle se produit dans cette couche.

Votre vue affiche simplement les modèles que vous avez préparés. Essentiellement, la vue ne lit que le modèle et ajuste sa sortie en conséquence.

Mapper le principe général aux classes réelles

Rappelez-vous que vos dialogues sont des vues. Si vous avez déjà une classe de dialogue, il n'y a aucune raison de créer une autre classe "View". La couche Presenter lie essentiellement le modèle aux contrôles de la vue. La logique applicative et toutes les données importantes sont stockées dans le modèle.

Berin Loritsch
la source