J'essaie de bien comprendre comment mettre en œuvre un bon découplage entre une interface utilisateur et le modèle, mais j'ai du mal à déterminer exactement où diviser les lignes.
J'ai regardé Model-View-Presenter, mais je ne sais pas exactement comment procéder pour l'implémenter. Par exemple, ma vue comporte plusieurs boîtes de dialogue.
J'essaye de:
void(void)
je peux m'en tenir, au moins une application C # avec une bibliothèque C++ ...Alors .. des suggestions sur la façon de gérer les interactions?
Bienvenue sur une piste glissante. À ce stade, vous avez réalisé qu'il existe 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 Model View Presenter, comme la plupart des modèles architecturaux, est ouvert à beaucoup de variété et d'expérimentation. La seule chose que toutes les variations ont en commun est le rôle du présentateur comme "intermédiaire" entre la vue et le modèle. Les deux plus courants sont le Vue passive et le Présentateur/contrôleur superviseur - [ Fowler ]. Passive View traite l'interface utilisateur comme une interface très superficielle entre l'utilisateur et le présentateur. Il contient très peu ou pas de logique, déléguant autant de responsabilités à un présentateur. Superviseur présentateur/contrôleur essaie 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 le présentateur/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 procéder. Il est très courant de voir cela géré en traitant chaque boîte de dialogue/formulaire comme une vue différente. Plusieurs fois, il existe une relation 1: 1 entre les vues et les présentateurs. Ce n'est pas une règle dure et rapide. Il est assez courant qu'un seul présentateur gère plusieurs vues liées ou vice versa. 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, cela est parfois appelé 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 a des gestionnaires d'événements qui délogent vers un présentateur à l'aide d'appels de fonction directs:
MyForm.SomeEvent(Sender)
{
Presenter.DoSomething(Sender.Data);
}
Étant donné que le présentateur n'a pas de référence à la vue, la vue doit lui envoyer des données en tant qu'arguments. Le présentateur peut communiquer à nouveau avec la vue en utilisant des fonctions d'événements/de rappel que la vue doit écouter.
Le présentateur contient une référence à afficher
Dans le scénario, la vue expose les propriétés des données qu'elle affiche à 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 contiennent une référence l'un à l'autre formant une dépendance circulaire
Ce scénario est en fait plus facile à utiliser 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 à considérer avec les modèles MVP. Ordre de création, durée de vie de l'objet, où le câblage a lieu, communication entre les triades MVP mais cette réponse s'est déjà suffisamment développée.
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 en se concentrant 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'elle propose pour que le présentateur interagisse avec la vue. La vue est responsable de l'implémentation de ces méthodes ou propriétés comme elle le souhaite. Généralement, vous avez une vue: un présentateur mais dans certains cas, vous pouvez avoir plusieurs vues: un présentateur (web, wpf, etc.). La clé ici est que le présentateur ne sait rien des implémentations de l'interface utilisateur et n'interagit qu'avec la vue 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);
}
Voici maintenant le présentateur. Notez que le présentateur prend une IView dans son constructeur.
class Presenter
{
private IView _view;
public Presenter(IView view)
{
_view = view;
}
}
Voici maintenant 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 ne se soucie pas de la façon dont la vue implémente la méthode qu'elle fait. Pour tout ce que le présentateur sait, il pourrait s'agir d'écrire dans un fichier journal et même de ne pas le montrer à l'utilisateur.
Dans tous les cas, le présentateur travaille avec le modèle sur le back-end et souhaite à un moment donné informer l'utilisateur de ce qui se passe. Alors maintenant, nous avons une méthode quelque part dans le présentateur qui appelle le message 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 détient qu'une référence à une implémentation d'IView et ne se soucie pas vraiment de la façon dont il est implémenté.
C'est également une implémentation médiocre 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) comme Windsor, Ninject, etc. qui résoudraient la mise en œuvre d'IView pour vous lors de l'exécution à la demande et le rendraient ainsi encore plus découplé.
Je pense qu'il est important de se rappeler que le contrôleur/présentateur est l'endroit où l'action se déroule vraiment. Le couplage dans le contrôleur est inévitable en raison de la nécessité.
Le point central du contrôleur est que si vous modifiez la vue, le modèle ne doit pas changer et vice versa (si le modèle change, la vue ne doit pas non plus) parce que le contrôleur est ce qui traduit le modèle dans la vue et vice-versa. Mais le contrôleur changera lorsque les modifications du modèle ou de la vue le feront, car vous devez effectivement traduire dans le contrôleur la façon dont le modèle doit être 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 je peux également écrire une routine qui pousse les données extraites du modèle dans un string
à afficher dans le débogueur (et par extension dans un fichier texte brut). Si je peux prendre des données de modèle et les traduire librement en texte sans changer la vue ou le modèle et seulement le contrôleur, alors je suis sur la bonne voie.
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 les données de View to Push, la vue doit connaître le contrôleur pour lui indiquer quand une modification a été apportée (comme lorsque l'utilisateur clique sur "Enregistrer" ou "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.
Attention: Je viens d'un arrière-plan totalement Mac, Objective-C, Cocoa qui vous pousse vraiment dans le paradigme MVC, que vous le vouliez ou non.
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 quelques bonnes raisons à cela:
Dans votre contrôleur (application MVC), tout ce que vous faites est de collecter les modèles que vous devez utiliser dans votre vue et d'appeler les fonctions appropriées sur le 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.
Mappage du principe général aux classes réelles
N'oubliez pas 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 métier et toutes les données importantes sont stockées dans le modèle.