Dans le modèle MVP, la vue doit-elle instancier un objet modèle basé sur le contenu de l'interface utilisateur, ou simplement transmettre ces contenus en tant que paramètres au présentateur?

9

J'utilise le modèle MVP dans une application Android que je développe.

J'ai essentiellement 4 éléments:

  1. AddUserView où un nouvel utilisateur peut être ajouté:
  2. The AddUserPresenter
  3. Le UserInfo (le pojo)
  4. UserInfoManager (logique de gestion et gestionnaire de stockage)

Ma question est:

Lorsque j'appuie sur le bouton "Ajouter" dans AddUserView, il devrait obtenir le contenu des vues de texte, instancier une nouvelle UserInfo et la transmettre au présentateur. Ou bien AddUserView doit-il simplement récupérer le contenu textViews et le transmettre à AddUserPresenter, qui va en fait instancier l'UserInfo et le passer à l'UserInfoManager?

Rômulo.Edu
la source

Réponses:

8

Selon la description de MVP par Martin Fowler ( http://martinfowler.com/eaaDev/uiArchs.html )

À propos de la partie View de MVC, Fowler dit:

Le premier élément de Potel est de traiter la vue comme une structure de widgets, des widgets qui correspondent aux contrôles du modèle Forms and Controls et de supprimer toute séparation vue / contrôleur. La vue de MVP est une structure de ces widgets. Il ne contient aucun comportement décrivant comment les widgets réagissent à l'interaction de l'utilisateur .

(Mine d'accentuation en gras)

Puis du présentateur:

La réaction active aux actes de l'utilisateur vit dans un objet présentateur distinct. Les gestionnaires fondamentaux pour les gestes des utilisateurs existent toujours dans les widgets, mais ces gestionnaires ne font que passer le contrôle au présentateur .

Le présentateur décide ensuite comment réagir à l'événement. Potel aborde cette interaction principalement en termes d'actions sur le modèle, ce qu'il fait par un système de commandes et de sélections. Une chose utile à souligner ici est l'approche de l'empaquetage de toutes les modifications du modèle dans une commande - cela fournit une bonne base pour fournir un comportement d'annulation / rétablissement.

(Encore une fois, la mienne est en gras)

Ainsi, conformément aux directives de Fowler, votre vue ne devrait être responsable d'aucun comportement en réponse à l'événement de bouton; qui comprend la création d'une instance de UserInfo. La responsabilité de décider de créer un objet appartient à la méthode Presenter à laquelle l'événement d'interface utilisateur est transmis.

Cependant, on pourrait également affirmer que le gestionnaire d'événements de bouton de la vue ne devrait pas non plus être responsable de la transmission du contenu de votre textView, car la vue devrait simplement transmettre l'événement de bouton au présentateur et rien de plus.

Avec MVP, il est courant que la vue implémente une interface que le présentateur peut utiliser pour récupérer des données directement à partir de la vue (tout en s'assurant que le présentateur est toujours indépendant de la vue elle-même). Étant donné que UserInfo est un simple POJO, il peut être valide pour la vue d'exposer un getter pour UserInfo que le présentateur peut récupérer depuis la vue via une interface.

// The view would implement IView
public interface IView {

    public UserInfo GetUserInfo();
}

// Presenter
public class AddUserPresenter {

    private IView addUserView;

    public void SetView(IView view) {
        addUserView = view
    }

    public void onSomethingClicked() {

        UserInfo userInfo = addUserView.GetUserInfo();
        // etc.
    }
}

En quoi est-ce différent de passer UserInfodirectement dans la vue à l'aide du gestionnaire d'événements? La principale différence est que le présentateur est toujours en fin de compte responsable de la logique qui provoque la UserInfocréation d' un objet. c'est-à-dire que l'événement a atteint le présentateur avant la création du UserInfo, permettant au présentateur de prendre la décision.

Imaginez un scénario où vous aviez une logique de présentateur où vous ne vouliez pas que cela UserInfosoit créé en fonction d'un état dans la vue. Par exemple, si l'utilisateur n'a pas coché une case dans la vue, ou si vous avez effectué une vérification de validation par rapport à un champ à ajouter dans UserInfo qui a échoué - votre présentateur peut contenir une vérification supplémentaire avant d'appeler GetUserInfo- c'est-à-dire

    private boolean IsUsernameValid() {
        String username = addUserView.GetUsername();
        return (username != null && !username.isEmpty());
    }

    public void onSomethingClicked() {            

        if (IsUsernameValid()) {
            UserInfo userInfo = addUserView.GetUserInfo();
            // etc.
        }
    }

Cette logique reste à l'intérieur du présentateur et n'a pas besoin d'être ajoutée à la vue. Si la vue était responsable de l'appel, GetUserInfo()elle serait également responsable de toute logique entourant son utilisation; c'est ce que le modèle MVP essaie d'éviter.

Ainsi, alors que la méthode qui crée qui UserInfopeut physiquement exister dans la classe View, elle n'est jamais appelée à partir de la classe View, uniquement à partir du Presenter.

Bien sûr, si la création des UserInfofichiers finit par nécessiter des vérifications supplémentaires par rapport au contenu des widgets d'entrée utilisateur (par exemple, conversion de chaîne, validation, etc.), il serait préférable d'exposer des getters individuels pour ces choses afin que la validation / conversion de chaîne puisse prendre placer dans le présentateur - puis le présentateur crée votre UserInfo.

Dans l'ensemble, votre objectif principal en ce qui concerne la séparation entre Presenter / View est de vous assurer que vous n'avez jamais besoin d'écrire de logique dans la vue. Si jamais vous avez besoin d'ajouter une ifdéclaration pour une raison quelconque (même s'il s'agit d'une ifdéclaration concernant l'état d'une propriété de widget - vérifier une zone de texte vide ou un booléen pour une case à cocher), alors elle appartient au présentateur.

Ben Cottrell
la source
1
Grande réponse @BenCottrell! Mais j'en ai une autre :) Est-ce une bonne pratique de nommer les méthodes du présentateur comme onSomethingClicked(), donc lorsque l'utilisateur clique sur "quelque chose", la vue appelle presenter.onSomethingClicked()? Ou mes méthodes de présentation devraient être nommées comme les actions prévues, dans mon cas addUser()?
Rômulo.Edu
1
@regmoraes Bonne question; et je pense que vous avez mis en évidence une légère odeur dans mon exemple de code. Le Presenterest bien sûr responsable de la logique d'interface utilisateur plutôt que de la logique de domaine, et spécialement adapté à la View, donc les concepts qui devraient exister sont des concepts d'interface utilisateur, donc une méthode nommée onSomethingClicked()est en effet appropriée. Avec le recul, la dénomination que j'ai choisie dans mon exemple ci-dessus ne sent pas très bien :-).
Ben Cottrell
@BenCottrell Tout d'abord merci beaucoup pour la bonne réponse. Je comprends, c'est valable d'avoir cette GetUserInfométhode dans la vue comme vous l'avez mentionné (sera déclenchée par le présentateur) Qu'en est-il des ifconditions possibles à l' intérieur de la GetUserInfométhode? Peut-être que certains champs de UserInfo seront définis via la réaction de l'utilisateur? Un scénario: peut - être que l'utilisateur sélectionne une case à cocher, puis certains nouveaux composants (un nouveau EditText peut-être) seront visibles par l'utilisateur. Donc, dans ce cas, la GetUserInfométhode aura une condition if. Dans ce scénario GetUserInfoest toujours valide?
blackkara
1
@Blackkara Envisagez de traiter UserInfocomme un modèle de la vue (alias "Afficher le modèle") - Dans ce scénario, j'ajouterais l' booleanétat de la case à cocher et l'état vide / annulable Stringde la zone de texte UserInfo. Vous pourriez même envisager de le renommer UserInfoViewModelsi cela aide à penser en termes de POJO étant une classe dont le seul véritable objectif est de laisser les UserInfoPresenterinformations sur l'état de la vue.
Ben Cottrell