web-dev-qa-db-fra.com

MVVM: connexion ViewModel et Business Logic

Après avoir fait quelques projets en utilisant le modèle MVVM, je suis toujours aux prises avec le rôle du ViewModel:

Ce que j'ai fait par le passé: Utiliser le modèle uniquement en tant que conteneur de données . Mettre la logique pour manipuler les données dans le ViewModel. (C'est la logique d'entreprise, n'est-ce pas?) Contre: la logique n'est pas réutilisable.

Ce que j'essaie maintenant: Garder le ViewModel aussi fin que possible . Déplacer toute la logique dans la couche de modèle . Ne garder que la logique de présentation dans le ViewModel . Con: rend la notification d'interface utilisateur vraiment pénible. Si les données sont modifiées dans la couche de modèle.

Je vais donc vous donner un exemple pour le rendre plus clair:

Scénario: Outil pour renommer des fichiers. Classes: Fichier: Représentant chaque fichier; Règle: Contient Logic comment renommer un fichier;

Si je suis l'approche suivante 1: Création d'un ViewModel pour le fichier, la règle et la vue -> RenamerViewModel . Insertion de toute la logique dans RenamerViewModel: Contenant une liste de FileViewModel et de RuleViewModel et de la logique en cours. Facile et rapide, mais non réutilisable.

Si je suis l'approche suivante 2: Création d'une nouvelle classe de modèle -> Renamer, qui contient une liste de fichiers, une règle et la logique précédente pour interagir sur chaque fichier et appliquer chaque règle . Création d'un modèle de vue pour Fichier, règle et Renamer . Maintenant, RenamerViewModel contient uniquement une instance du modèle Renamer, plus deux ObservableCollections pour envelopper la liste de fichiers et de règles de Renamer . Mais toute la logique est dans le modèle Renamer. Ainsi, si le modèle Renamer est déclenché pour manipuler certaines données à l'aide d'appels de méthode, le ViewModel n'a pas d'indication sur le type de données manipulées. Le modèle ne contient aucune notification PropertyChange et je l'éviterai donc. La logique est séparée, mais cela rend difficile la notification de l'interface utilisateur.

36
JDeuker

Inscrire la logique métier dans le modèle de vue est une très mauvaise façon de procéder. Je vais donc dire rapidement ne jamais faire cela et passer à la discussion sur la deuxième option.

Il est beaucoup plus raisonnable d'intégrer la logique dans le modèle et c'est une bonne approche de départ. Quels sont les inconvénients? Votre question dit

Donc, si le modèle Renamer est déclenché pour manipuler des données par méthode Appels, le ViewModel n’a aucune idée des données manipulées. Parce que le modèle ne contient aucune notification PropertyChange et je le ferai. éviter cela.

Eh bien, rendre votre modèle implémenté INotifyPropertyChanged vous permettrait certainement de passer à de meilleures choses. Cependant, il est vrai qu'il est parfois impossible de le faire. Par exemple, le modèle peut être une classe partielle dans laquelle les propriétés sont générées automatiquement par un outil et ne génèrent pas de notifications de modification. C'est malheureux, mais pas la fin du monde. 

Si vous voulez acheter quelque chose, alors quelqu'un doit le payer; si ce n'est pas le modèle qui donne de telles notifications, il ne vous reste que deux choix:

  1. Le modèle de vue sait quelles opérations sur le modèle (éventuellement) entraînent des modifications et il met à jour son état après chaque opération.
  2. Une autre personne sait quelles opérations entraînent des modifications et avise le modèle de vue de mettre à jour son état une fois le modèle modifié.

La première option est à nouveau une mauvaise idée car elle revient en réalité à intégrer la "logique métier" dans le modèle de vue. Pas aussi grave que de mettre all la logique métier dans le modèle de vue, mais quand même.

La deuxième option est plus prometteuse (et, malheureusement, plus de travail à mettre en œuvre):

  • Placez une partie de votre logique métier dans une classe distincte (un "service"). Le service implémentera toutes les opérations commerciales que vous souhaitez effectuer en utilisant les instances de modèle, le cas échéant.
  • Cela signifie que le service sait quand les propriétés du modèle peuvent changer (OK: modèle + service == logique métier).
  • Le service fournira des notifications sur les modèles modifiés à toutes les parties intéressées; vos modèles de vue dépendront du service et recevront ces notifications (pour qu'ils sachent quand "leur" modèle a été mis à jour).
  • Étant donné que les opérations commerciales sont également implémentées par le service, cela reste très naturel (par exemple, lorsqu'une commande est invoquée sur le modèle de vue, la réaction appelle une méthode appropriée sur le service; rappelez-vous que le modèle de vue lui-même ne connaît pas la logique métier).

Pour plus d'informations sur une telle implémentation, voir aussi mes réponses ici et ici .

45
Jon

Les deux approches sont valables, mais il existe une troisième approche: implémenter un service entre le modèle et les couches VM. Si vous souhaitez garder vos modèles stupides, un service peut fournir un intermédiaire indépendant de l'interface utilisateur qui peut appliquer vos règles métier de manière réutilisable.

Parce que le modèle ne contient aucune notification PropertyChange et que je vais éviter cela

Pourquoi évitez-vous cela? Ne vous méprenez pas, j'ai tendance à garder mes modèles aussi bête que possible, mais implémenter une notification de modification dans votre modèle peut parfois être utile, et vous ne dépendez que de System.ComponentModel lorsque vous le faites. C'est complètement agnostique.

12
Kent Boogaart

Je fais ce qui suit

  1. Afficher avec la logique d'affichage XAML uniquement

  2. ViewModel qui gère les gestionnaires de clics et crée de nouveaux modèles de vues. Gère les événements routés, etc. 

  3. Modèle, qui est mon conteneur de données et ma logique métier pour la validation des données du modèle.

  4. Services qui remplissent le modèle avec des données. Par exemple, appelez un serveur Web, chargez-le à partir du disque, enregistrez-le sur un disque, etc. Selon l'exemple souvent, mon modèle et mon service implémenteront IPropertyChanged. Ou ils peuvent avoir des gestionnaires d'événements à la place. 

Toute application complexe nécessite une autre couche. Je l'appelle modèle + service, vue, vue, modèle. Le service résume votre logique métier et prend une instance de modèle comme dépendance ou crée un modèle. 

1
rolls

Vous pouvez également implémenter IDataErrorInfo sur Model et ViewModel, mais la validation ne sera effectuée que dans Model, ce qui facilitera la mise en œuvre des règles de gestion uniquement dans Model ...

Ex:

ViewModel:

...

private Person person;

...

string IDataErrorInfo.this[string propertyName]
{
    get
    {
        string error = (person as IDataErrorInfo)[propertyName];
        return error;
    }
}

Modèle:

public class Person:INotifyPropertyChanged,IDataErrorInfo
{

...

   string IDataErrorInfo.this[string propertyName]
   {
        get { return this.GetValidationError(propertyName); }
   }

...

   string GetValidationError(string propertyName)
   {
        if(propertyName == "PersonName")
             //do the validation here returning the string error
   }
}

De plus, jetez un coup d’œil au modèle MVCVM, utilisez-le réellement et c’est bien beau d’abstraire la logique métier en une classe de contrôleur au lieu du modèle ou du modèle de vue.

0
Rafael A. M. S.