Dans une application wpf, un objet Model
est créé à partir de zéro ou désérialisé à partir d'un fichier .xml
. Pour créer une connexion au ViewModel
, la méthode suivante (sur le Model
!) Est utilisée par l'application:
class Model
{
public override UserControl View
{
get
{
if (_view == null)
{
ViewModel viewModel = new ViewModel(this);
_view = new View();
_view.DataContext = viewModel;
}
return _view;
}
}
}
Jusqu'à présent, donc pasmvvm . Le Model
ne doit pas connaître (= avoir une référence directe à) le ViewModel
, sans parler du View
.
Briser le modèle de cette façon a servi de raccourci pratique, mais commence à faire des ravages. Je voudrais me débarrasser de cette méthode pour séparer correctement les préoccupations comme prévu par le modèle.
Mais comment ferais-je cela? La fonctionnalité de la méthode en elle-même semble correcte en mmvm. Si je le retirais de Model
et le plaçais dans l'application, cela ressemble au bon code mvvm-ish qui connecte les composants:
ViewModel viewModel = new ViewModel(new Model);
_view = new View();
_view.DataContext = viewModel;
Mais je ne peux pas faire cela, car je ne sais pas à quelle classe Model
je fais face, lorsque j'examine le problème du point de vue de l'application, car l'une des plusieurs classes Model
peut obtenir instancié ou désérialisé. Dans un certain sens, le Model
sait le mieux ce que ViewModel
à utiliser (d'où l'implémentation actuelle), mais c'est exactement la dépendance que j'essaie d'éviter.
Comment construire les parties du modèle mvvm "à contre-courant" des dépendances du modèle? Autrement dit, passer de Model
à View(Model)
et pas les autres inverse.
Dictionary
) à associer un ViewModel
spécifique qui devrait être utilisé avec un spécifique Model
, mais cela signifie une maintenance lors de l'ajout de classes, des erreurs, etc.FooModel
ira de pair avec un FooModelView
, ce qui serait une amélioration, mais est toujours sujet à casser.ViewModel
pourrait [Import]
Un IModel
que le Model
pourrait alors [export]
, mais le Model
(= la dépendance) est créé en premier, donc je chercherais des importations qui seraient satisfaites d'une exportation donnée et non l'inverse. Bien que ces outils aideraient à diviser les préoccupations entre différents assemblages (ce qui est bon et un objectif aussi), ils ne résolvent pas le problème sous-jacent que je ne sais pas comment commencer cette séparation en premier lieu, ce qui n'est pas 't leur but de toute façon. Si je permute l'importation et l'exportation, je suppose que je finirais avec ce que j'ai commencé.Existe-t-il de meilleures solutions que de créer une association entre les deux (soit en codant en dur soit par réflexion) dans l'application?
pouvez-vous expliquer comment et pourquoi chaque ViewModel ne connaît pas déjà la classe Model appropriée à instancier et les méthodes Model appropriées à appeler? N'est-ce pas l'une des principales raisons d'avoir des classes ViewModel en premier lieu?
Chaque ViewModel
connaît la classe Model
appropriée. Je suis désolé si cela ne ressort pas clairement du texte. Dans l'exemple de code, le Model
est passé au constructeur du ViewModel
. ViewModel
ne pas instancie l'objet Model
car cet objet pourrait être construit ailleurs (par exemple, il pourrait provenir d'un processus de désérialisation). C'est en effet la principale raison de son existence.
Le problème est de décider quel ViewModel
créer, de sorte que le Model
(d'un type qui est décidé au moment de l'exécution) puisse lui être transmis.
En supposant que vous avez l'intention d'utiliser une forme d'IoC pour résoudre ce problème, ne pourriez-vous pas simplement utiliser la configuration du conteneur DI pour identifier le modèle requis, ou utiliser un localisateur de service?
Pouvez-vous également clarifier davantage la phrase "Briser le schéma de cette manière a servi de raccourci pratique, mais commence à faire des ravages"?
Étant donné que le Model
lui-même garantissait que le bon ViewModel
(et View
) serait instancié, la valeur renvoyée pourrait être négligemment ajoutée à l'écran. Le câblage correct des composants s'est produit dans la méthode vue dans le premier exemple de code. Voilà pour la commodité de pouvoir "afficher" un Model
. Le prix à payer est que Model
utiliserait et donc référencerait View
et ViewModel
, qui sont des classes qui dépendent des bibliothèques GUI, que le Model
devrait pas dépendre.
ou, pour être franc: pourquoi cette application console a-t-elle besoin de 5 DLL pour les composants de l'interface utilisateur graphique?
ne pourriez-vous pas simplement utiliser la configuration du conteneur DI pour identifier le modèle requis, ou utiliser un localisateur de service?
Je suppose que la structure de données mentionnée qui associe ViewModel
à Model
est un localisateur de services (pour les pauvres). Je suis nouveau sur ce modèle, mais à première vue, cela semble être le cas. Utiliser le type attendu de ViewModel
s pour le construire est quelque chose auquel je n'ai pas pensé. (Je pense et pas ou concernant votre question) En ce qui concerne cette approche, je prendrais votre suggestion comme réponse. J'espérais cependant une solution un peu moins DIY-ish. En voyant comment la liaison fait beaucoup de choses automatiquement, je suis quelque peu surpris de constater que le fait d'aller dans la direction opposée (c'est-à-dire d'avoir un Model
pour commencer) nécessite une telle gestion manuelle des dépendances. Mais alors c'est peut-être ce qu'il faut.
Voici mes suggestions:
Suggestion 1
Vous pouvez essayer d'utiliser des méthodes d'extension afin de séparer les préoccupations et, à la fin, effectuer exactement ce dont vous avez besoin (modèles qui connaissent leurs modèles de vue respectifs):
using System;
using System.Collections.Generic;
namespace Yay
{
public class BaseModel
{
public bool CommonStuff { get; set; }
}
public class FromScratchModel : BaseModel
{
public string A { get; set; }
public string B { get; set; }
}
public class DeserializedModel : BaseModel
{
public int C { get; set; }
public bool D { get; set; }
}
public class BaseViewModel
{
public override string ToString()
{
return "Base View Model!!!!";
}
}
public class ViewModel1 : BaseViewModel
{
public override string ToString()
{
return "View Model - From Scratch!!!!";
}
}
public class ViewModel2 : BaseViewModel
{
public override string ToString()
{
return "View Model - Deserialized!!!!";
}
}
static class Extensions
{
public static BaseViewModel GetViewModel(this BaseModel model)
{
return new BaseViewModel();
}
public static BaseViewModel GetViewModel(this FromScratchModel model)
{
return new ViewModel1();
}
public static BaseViewModel GetViewModel(this DeserializedModel model)
{
return new ViewModel2();
}
}
class Program
{
static void Main(string[] args)
{
IList<BaseModel> models =
new List<BaseModel> { new BaseModel(),
new FromScratchModel(),
new DeserializedModel() };
foreach (var model in models)
{
//Calling model.GetViewModel() will not work here; already tried (it runs, but it will return the BaseViewModel always).
//But it's possible to call the right method resolved at runtime
//using dynamic keyword
BaseViewModel viewModel = Extensions.GetViewModel(model as dynamic);
Console.WriteLine(viewModel.ToString());
}
Console.ReadLine();
}
}
}
Suggestion 2
Vous avez différents modèles qui peuvent arriver, qui sont résolus au moment de l'exécution. Dans cette option, je suggère:
Ce qui suit est le résultat de mes délibérations au cours des deux derniers jours. La solution qui en résulte est une idée, qui contient également certains problèmes que j'ai déjà découverts. J'espère avoir une discussion pour savoir si la structure et la conception de la solution sont bonnes et quels (anti) schémas elle peut réaliser. Puisque mes idées proviennent de ce post, je vais le poster comme une réponse ici, mais j'ai aussi pensé à poser une nouvelle question (lien vers celle-ci).
Eh bien, nous voulons y parvenir modèles sont complètement indépendants de voir les modèles (encore moins de vues) et inférer encore à un, si nous en avons besoin. Imaginons que nos modèles soient POCO s:
class Base { /* some properties */ }
class Special : Base { /* more properties */ }
class ExtraSpecial : Special { /* even more properties */ }
Maintenant, nous utilisons une approche générique simple comme classe de base pour nos voir les modèles:
abstract class ViewModel<ModelType>
{
protected ModelType Model;
public ViewModel(ModelType model)
{
this.Model = model;
}
}
class BaseViewModel : ViewModel<Base> { /* constructor and view model stuff */ }
class SpecialViewModel : ViewModel<Special> { /* constructor and view model stuff */ }
Maintenant, nos voir les modèles connaissent le type de modèle, qu'ils doivent présenter à la vue respective. Le view sait seulement quoi montrer pour quel view-model.
Revenons au problème dans la question. Nous obtenons un nouveau modèle (de n'importe où, par exemple désérialisation) et nous savons qu'il s'agit d'un Base
, mais nous ne savons pas lequel Base
. Nous pourrions simplement utiliser un BaseViewModel
, mais s'il s'agit d'un Special
, nous conserverons tout au plus des fonctionnalités lâches. Nous voulons vérifier quel type de Base
est vraiment notre modèle. Pour un cas simple, nous pourrions utiliser quelques branches if
-, mais cela deviendra très laid bientôt. En utilisant la réflexion, nous pourrions trouver un nom voir le modèle via le nom modèle, mais les règles de nommage (et non les conventions) sont merdiques neuf fois sur dix. Nous pourrions établir une relation entre modèles et voir les modèles ... peut-être utiliser un dictionnaire ... ou les enregistrer dans une sorte de localisateur de service ... mais attendez! Nous avons déjà construit une relation via des génériques, de nos voir les modèles aux modèles. Ne pourrions-nous pas simplement vérifier quel voir le modèle est capable de gérer notre type modèle?
Tout d'abord, nous collectons tous les voir les modèles ...
HashSet<Type> types = new [] { typeof(BaseViewModel), typeof(SpecialViewModel) };
// or we collect them from some Assembly or namespace
Ensuite, nous extrayons le type modèle pour chacun d'eux:
for (; type != null; type = type.BaseType)
{
if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(ViewModel<>)))
{
return type.GetGenericArguments().Single();
}
}
return null;
Et enfin, nous pouvons vérifier si cela correspond à notre cible modèle:
type.IsAssignableFrom(modelTypeTarget)
Premier problème: Si nous ne vérifions que l'assignation (au lieu de l'égalité), nous trouverons plusieurs possibles voir les modèles pour a modèle, nous devons décider lequel utiliser.
Solution: Nous calculons une distance d'héritage (et la minimisons) pour déterminer laquelle voir le modèle correspond à a modèle le meilleur:
private static uint GetInheritanceDistance(Type type, Type parent)
{
uint i = 0;
for (; type != null; type = type.BaseType)
{
if (type.Equals(parent))
{
return i;
}
i++;
}
return uint.MaxValue;
}
De cette façon, un ExtraSpecial
obtiendra un SpecialViewModel
assigné, pas un BaseViewModel
.
Deuxième problème : Que se passe-t-il si nous avons besoin (ou voulons) plusieurs voir les modèles pour un modèle, par exemple un pour afficher une entrée et l'autre pour l'édition?
Solution : Nous introduisons des contextes. Ces contextes peuvent être attribués aux types voir le modèle via des attributs:
[ViewModelContext(Context.View)]
class BaseViewModel : ViewModel<Base> { /* ... */ }
[ViewModelContext(Context.Edit)]
class EditBaseViewModel : ViewModel<Base> { /* ... */ }
Maintenant, nous ne considérons que les modèles de vue qui correspondent au contexte dont nous avons besoin dans notre cas spécifique.
Juste pour votre information, j'ai implémenté la fonctionnalité que j'ai décrite ci-dessus, donc je peux ajouter un lien de référentiel plus tard, mais pour l'instant je veux savoir si cela peut résoudre le problème décrit dans la question et s'il serait considéré comme un bon solution.