Est-ce que MVVM enfreint DRY?

Il semble que ViewModels que je fabrique ressemble étrangement à d’autres classes et qu’elles requièrent beaucoup de répétition de code, par exemple dans un projet en cours:

  • SmartForm : le modèle qui représente un formulaire de données à remplir a des propriétés:
    • Code d’identification
    • Titre
    • La description
    • collection de SmartFormFields
    • etc.
  • SmartFormControlView Voir
  • SmartFormControlViewModel ViewModel
    • Code d’identification
    • Titre
    • La description
    • collection de SmartFormFields
    • etc.

Donc, mon ViewModel est fondamentalement le même que mon modèle , juste avec toutes les fonctionnalités OnPropertyChanged pour la liaison avec View.

Il me semble que lorsque je réorganise et étends cela à chaque petit changement apporté à mon modèle, je dois modifier le miroir ViewModel .

Cela semble violer une règle de base des motifs Ne pas répéter .

Est-ce que la mise en œuvre du modèle MVVM est incorrecte ou est-ce simplement une caractéristique inhérente à MVVM qu’il y a toujours une répétition 1: 1 entre Model et ViewModel?

Personnellement, je ne pense pas que cela viole DRY puisque le modèle et le modèle de vue (je préfère le terme présentateur) ne renvoient pas aux mêmes informations. Par exemple, votre VM et M ont toutes deux une propriété Title, mais la propriété Title de votre VM peut également inclure la validation, tandis que la propriété Title de votre modèle peut être valide.

S’il est vrai que la VM peut contenir toutes les propriétés du modèle, il est également possible d’avoir des validations (par exemple, le titre doit être non vide), des dépendances de données, des propriétés spécifiques à l’interface utilisateur pouvant être liées (icons, couleurs, pinceaux, etc.) qui ne font pas partie de la vue.

Essentiellement, tous les modèles d’interface utilisateur présentent une “duplication” similaire à celle que vous indiquez: à savoir les modifications en cascade. Essayez de changer un modèle dans MVC sans changer le contrôleur.

Cela étant dit, MVVM (ou tout modèle d’interface utilisateur conçu pour séparer l’interface utilisateur, la logique et l’état) peut être trop fastidieux pour des cas simples tels que votre exemple. Lorsque la logique devient un peu plus que le passage d’état, la valeur séparant le contrôleur / présentateur / modèle de vue diminue.

Dans votre cas spécifique, s’il n’y a pas vraiment de logique, de validation ou de propriétés spécifiques à l’interface utilisateur que votre machine virtuelle ne génère, et que votre modèle ne doit pas être persistant, sérialisé ou rendu compatible avec une structure existante ( ou append la logique pour le faire dans votre VM est sortingvial), je considérerais fortement combiner le M et le VM pour éviter de créer des propriétés dont le seul but est d’obtenir / définir les propriétés du modèle sous-jacent.

Une solution simple consiste à avoir une classe de base abstraite ViewModel (VM) qui expose le modèle. Vous pouvez choisir cette VM dans des scénarios où cela a du sens.

c’est à dire

public abstract class ViewModelBase { public T Model { get; set; } } 

Si votre modèle a INotifyPropertyChanged mis en œuvre, votre vue obtiendra l’événement. Cela permet à votre View d’accéder à toutes les propriétés de votre modèle, ce qui n’est pas ce que vous voulez parfois.

Vous pouvez également utiliser des initialiseurs de propriétés comme celui-ci (que j’ai personnellement stockés dans des extraits de code):

 public abstract class SampleViewModel { public int MyProperty { get { return Model.MyProperty; } set { Model.MyProperty = value; OnPropertyChanged("MyProperty"); } } } 

Dans la plupart des cas, vous verrez que celui qui apportera des modifications à votre machine virtuelle sera averti que tout contrôle lié à cette propriété sera alors transmis.

J’espère que cela pourra aider.

C’est une remarque intéressante … en effet, il est souvent nécessaire de modifier le ViewModel pour refléter les modifications du modèle.

Ce serait bien si cela pouvait être automatique … en fait, je pense que cela pourrait être possible, en implémentant ICustomTypeDescriptor dans ViewModel: GetProperties renverrait toutes les propriétés du modèle par reflection. Cependant, je ne suis pas sûr que cela ait un sens, car le modèle peut ne pas comporter de propriétés du tout: il peut s’agir de méthodes, de champs ou de tout autre élément, et tout ce qui est dans le modèle ne serait pas utile dans ViewModel.

Une chose qui semble avoir été manquée ici et que votre exemple simpliste n’expose pas est le fait que vos vues agrègent souvent les données contenues dans plusieurs types de modèles de domaine. Dans ce cas, vos ViewModels contiendront des références à un certain nombre de modèles de domaine (de types différents), regroupant ainsi un dataset associées qu’une vue particulière pourrait souhaiter exposer.

D’autres ont fourni de bons commentaires sur les rôles des composants des modèles MVC / MVVM. Je voudrais offrir une observation fondamentale expliquant la répétitivité, quel que soit le modèle que vous sélectionnez.

En général, il y aura une sorte de répétition entre votre couche de données, votre couche de gestion et votre couche d’interface utilisateur. Après tout, en général, vous devez montrer chaque propriété à l’utilisateur final, modéliser son comportement (couche Business) et conserver la valeur (couche de données).

Comme d’autres l’ont souligné, la propriété pourrait être traitée un peu différemment sur chaque couche, ce qui explique le besoin fondamental de certaines duplications.

Lorsque je travaille sur des systèmes suffisamment grands (ou sur de petits projets avec la bonne équipe), j’ai tendance à modéliser ce type d’informations en UML et à utiliser la génération de code (souvent associée à des classes partielles) pour traiter les aspects répétitifs. Comme exemple simple, la propriété Nom de famille peut exiger (dans mon modèle UML) de limiter les données à 50 caractères. Je peux générer du code pour appliquer cette limite dans ma couche d’interface utilisateur (par exemple en limitant physiquement les entrées), générer du code dans ma couche de gestion pour vérifier cette limitation (“ne jamais faire confiance à l’interface utilisateur”) en générant une exception et générer ma couche de persistance (par exemple, colonne NVARCHAR (50), fichier de mappage ORM approprié, etc.).

Mise à jour 2012

Les annotations de données de Microsoft et leur prise en charge dans la couche d’interface utilisateur (par exemple, ASP.Net MVC ) et sur la couche de données ( Entity Framework ) consortingbuent grandement à mettre en œuvre un grand nombre des problèmes précédemment générés.

Eric Evans, dans son livre “Domain Driven Design” mentionne que le refactoring de Model ne devrait pas être trop difficile, et qu’un changement de concept ne devrait pas s’étendre à trop de modules, sinon la refactorisation du Model deviendrait prohibitive. Le type de modèle ‘copié’ dans le ViewModel est certainement difficile à refaire.

Eric mentionne qu’il faut donner plus de poids à la cohésion et à l’isolement des modèles, plutôt qu’à la propreté des divisions en fonction de problèmes techniques (access aux bases de données, POCOS, présentation). La principale préoccupation est que le modèle de domaine soit une bonne représentation du domaine d’activité, c’est pourquoi il est le plus important que le modèle de domaine se trouve dans une seule couche isolée, et qu’il ne soit pas réparti sur plusieurs modules. mis à jour (refactorisé).

Compte tenu de ce qui vient d’être dit, j’utiliserais le même object Model dans ViewModel, et si je veux réduire le niveau “d’access” à un object Model, alors je “passerais” une référence à une interface implémentée par le Objet modèle Par exemple:

  // The primary Model classes public partial class OrderItem { public int Id { get; } public int Quantity { get; set; } public Product Item { get; set; } public int Total { get; set; } public void ApplyDiscount(Coupon coupon) { // implementation here } } public partial class Product { public int Id { get; } public ssortingng Name { get; set; } public ssortingng Description { get; set; } public decimal Price { get; set; } } // The shared views of those model classes public partial class OrderItem : IOrderItemDTO { public IProductDTO Item { get { return this.product; } } } public partial class Product : IProductDTO { } // Separate interfaces... // You enforce the rules about how the model can be // used in the View-ViewModel, without having to rewrite // all the setters and getters. public interface IOrderItemDTO { int Id { get; } int Quantity { get; set; } IProductDTO Item { get; } int Total { get; } } public interface IProductDTO { ssortingng Name { get; } ssortingng Description { get; } decimal Price { get; } } // The viewmodel... public class OrderItemViewModel { IOrderItemDTO Model { get; set; } } 

Je ne connais que MVC et dans MVC, une classe de modèle contenant une interface graphique est une erreur. SmartForm semble être un formulaire, ce qui signifie qu’il ne s’agit pas d’un modèle. Je ne sais pas ce que vous essayez de programmer, mais je vous donne un exemple pour un modèle:

Prenez un calendrier. Vous pouvez demander à la classe quelle date il est aujourd’hui, quel mois, combien de jours chaque mois, etc. La vue (CalenderViewMonth ou whater que vous voulez) imprime un seul mois à l’écran. Il connaît un calendrier et lui demande quoi écrire dans les différentes cellules.

Essentiellement, votre modélisation / compréhension de MVVM (qui est une variante .NET moderne de MVC) peut avoir quelque chose de mal.


Modifier:

Je viens de regarder MVVM sur Wikipedia. Le modèle est comme le modèle dans MVC. Voir comme la vue dans MVC aussi bien – seulement représentation graphique. le ViewModel est le collage entre une vue générique et un modèle spécialisé. Une sorte d’adaptateur. Il ne devrait y avoir aucune violation de DRY.

Je pense que oui, le MVVM vanille viole le DRY. Mais j’ai commencé la bibliothèque PDX qui, je pense, peut résoudre ce problème dans de nombreux cas. J’ai écrit ce post qui, je crois, répond à cette question.

En gros, mon objective (l’un d’eux) est d’avoir des modèles View qui ne s’inquiètent pas de la notification de l’interface utilisateur. Le projet PDX en est encore à ses débuts, mais si vous lisez cette question, vous le trouverez peut-être utile, et j’apprécierais vos commentaires.