2009-06-22 9 views
2

J'ai une application WPF où PageItems sont des objets modèles.Comment puis-je émuler l'héritage multiple et utiliser la réflexion pour optimiser ce code?

Mon ViewModel principal a une ObservableCollection de PageItemViewModels, chacun se construisant lui-même à partir de son objet modèle PageItem correspondant.

Chaque PageItemViewModel hérite de la classe abstraite BaseViewModel afin d'obtenir la fonctionnalité INotifyPropertyChanged.

Chaque PageItemViewModel met également en œuvre le IPageItemViewModel afin de vous assurer qu'il a les propriétés nécessaires.

je vais finalement avoir environ 50 pages, donc je veux éliminer tout code inutile:

  • SOLVED (voir plus bas): est-il un moyen que je peux obtenir des cours de PageItemViewModel à Hériter idcode et Titre donc je n'ai pas à les implémenter dans chaque classe? Je ne peux pas les mettre dans BaseViewModel car d'autres ViewModels en héritent et n'ont pas besoin de ces propriétés, et je ne peux pas les mettre dans IPageItemViewModel car c'est seulement une interface. Je comprends que je dois l'héritage multiple pour ce qui C# ne supporte pas
  • SOLVED (voir plus bas): déclaration est-il un moyen que je peux me débarrasser du commutateur, par exemple en quelque sorte utiliser réflexion à la place?

Ci-dessous est une application autonome Console qui montre le code que j'ai dans mon application WPF:

using System.Collections.Generic; 

namespace TestInstantiate838 
{ 
    public class Program 
    { 
     static void Main(string[] args) 
     { 
      List<PageItem> pageItems = PageItems.GetAll(); 
      List<ViewModelBase> pageItemViewModels = new List<ViewModelBase>(); 

      foreach (PageItem pageItem in pageItems) 
      { 
       switch (pageItem.IdCode) 
       { 
        case "manageCustomers": 
         pageItemViewModels.Add(new PageItemManageCustomersViewModel(pageItem)); 
         break; 
        case "manageEmployees": 
         pageItemViewModels.Add(new PageItemManageEmployeesViewModel(pageItem)); 
         break; 
        default: 
         break; 
       } 
      } 
     } 
    } 

    public class PageItemManageCustomersViewModel : ViewModelBase, IPageItemViewModel 
    { 
     public string IdCode { get; set; } 
     public string Title { get; set; } 

     public PageItemManageCustomersViewModel(PageItem pageItem) 
     { 

     } 
    } 

    public class PageItemManageEmployeesViewModel : ViewModelBase, IPageItemViewModel 
    { 
     public string IdCode { get; set; } 
     public string Title { get; set; } 

     public PageItemManageEmployeesViewModel(PageItem pageItem) 
     { 

     } 
    } 

    public interface IPageItemViewModel 
    { 
     //these are the properties which every PageItemViewModel needs 
     string IdCode { get; set; } 
     string Title { get; set; } 
    } 

    public abstract class ViewModelBase 
    { 
     protected void OnPropertyChanged(string propertyName) 
     { 
      //this is the INotifyPropertyChanged method which all ViewModels need 
     } 
    } 

    public class PageItem 
    { 
     public string IdCode { get; set; } 
     public string Title { get; set; } 
    } 

    public class PageItems 
    { 
     public static List<PageItem> GetAll() 
     { 
      List<PageItem> pageItems = new List<PageItem>(); 
      pageItems.Add(new PageItem { IdCode = "manageCustomers", Title = "ManageCustomers"}); 
      pageItems.Add(new PageItem { IdCode = "manageEmployees", Title = "ManageEmployees"}); 
      return pageItems; 
     } 
    } 

} 

Refonte: l'interface a changé de classe abstraite

using System; 
using System.Collections.Generic; 

namespace TestInstantiate838 
{ 
    public class Program 
    { 
     static void Main(string[] args) 
     { 
      List<PageItem> pageItems = PageItems.GetAll(); 
      List<ViewModelPageItemBase> pageItemViewModels = new List<ViewModelPageItemBase>(); 

      foreach (PageItem pageItem in pageItems) 
      { 
       switch (pageItem.IdCode) 
       { 
        case "manageCustomers": 
         pageItemViewModels.Add(new PageItemManageCustomersViewModel(pageItem)); 
         break; 
        case "manageEmployees": 
         pageItemViewModels.Add(new PageItemManageEmployeesViewModel(pageItem)); 
         break; 
        default: 
         break; 
       } 
      } 

      foreach (ViewModelPageItemBase pageItemViewModel in pageItemViewModels) 
      { 
       System.Console.WriteLine("{0}:{1}", pageItemViewModel.IdCode, pageItemViewModel.Title); 
      } 
      Console.ReadLine(); 
     } 
    } 

    public class PageItemManageCustomersViewModel : ViewModelPageItemBase 
    { 
     public PageItemManageCustomersViewModel(PageItem pageItem) 
     { 
      IdCode = pageItem.IdCode; 
      Title = pageItem.Title; 
     } 
    } 

    public class PageItemManageEmployeesViewModel : ViewModelPageItemBase 
    { 
     public PageItemManageEmployeesViewModel(PageItem pageItem) 
     { 
      IdCode = pageItem.IdCode; 
      Title = pageItem.Title; 
     } 
    } 

    public abstract class ViewModelPageItemBase : ViewModelBase 
    { 
     //these are the properties which every PageItemViewModel needs 
     public string IdCode { get; set; } 
     public string Title { get; set; } 
    } 

    public abstract class ViewModelBase 
    { 
     protected void OnPropertyChanged(string propertyName) 
     { 
      //this is the INotifyPropertyChanged method which all ViewModels need 
     } 
    } 

    public class PageItem 
    { 
     public string IdCode { get; set; } 
     public string Title { get; set; } 
    } 

    public class PageItems 
    { 
     public static List<PageItem> GetAll() 
     { 
      List<PageItem> pageItems = new List<PageItem>(); 
      pageItems.Add(new PageItem { IdCode = "manageCustomers", Title = "ManageCustomers"}); 
      pageItems.Add(new PageItem { IdCode = "manageEmployees", Title = "ManageEmployees"}); 
      return pageItems; 
     } 
    } 

} 

Répondre à l'élimination de l'instruction Switch:

Merci Jab:

string assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; 
string viewModelName = assemblyName + ".ViewModels.PageItem" + StringHelpers.ForcePascalNotation(pageItem.IdCode) + "ViewModel"; 
var type = Type.GetType(viewModelName); 
var viewModel = Activator.CreateInstance(type, pageItem) as ViewModelBase; 
AllPageViewModels.Add(viewModel); 

Répondre

1

Une solution qui n'est pas très jolie, mais qui fonctionne, consisterait à utiliser convention pour se débarrasser de l'instruction switch. Cela suppose que vous pouvez modifier les IdCodes ou au moins modifier le cas pour correspondre au ViewModel.

var type = Type.GetType("PageItem" + pageItem.IdCode + "ViewModel"); 
    var viewModel = Activator.CreateInstance(type) as ViewModelBase; 
    pageItemViewModels.Add(viewModel); 

Notez que vous devez ajouter une vérification d'erreur ici, il y a quelques points de défaillance ici. Il vaut toutefois mieux que d'avoir à maintenir une déclaration de changement sans cesse croissante.

+0

c'est juste l'approche pragmatique que je cherchais, j'ai dû tweek le code un peu que j'ai posté ci-dessus, merci! –

+0

Je suis content que tu aimes ça. Bien que je pencherais vers la solution de Mike Spross à long terme. La responsabilité est plus claire là-bas. – Jab

+0

Je dois dire que je ne suis pas d'accord avec la réponse. C'est pragmatique sans doute, mais pas une approche très commune dans le code de production. – Groo

2

Pouvez-vous créer une classe qui hérite de BaseViewModel qui mettra en œuvre ces deux propriétés - vos cours de PageItemViewModel qui ont besoin ce pourrait alors hériter de cela.

+0

grâce qui fonctionne, j'inclus solution ci-dessus, je voudrais encore se débarrasser de l'instruction switch, est-il possible d'instancier des objets dynamiquement qui héritent d'une classe abstraite commune? –

1

Comme suggéré par Paddy, je viens de créer une classe abstraite supplémentaire, PageViewModelBase, avec les auto-accessoires définis:

using System.Collections.Generic; 

namespace TestInstantiate838 
{ 
    public class Program 
    { 
     static void Main(string[] args) 
     { 
      List<PageItem> pageItems = PageItems.GetAll(); 
      List<ViewModelBase> pageItemViewModels = new List<ViewModelBase>(); 

      foreach (PageItem pageItem in pageItems) 
      { 
       switch (pageItem.IdCode) 
       { 
        case "manageCustomers": 
         pageItemViewModels.Add(new PageItemManageCustomersViewModel(pageItem)); 
         break; 
        case "manageEmployees": 
         pageItemViewModels.Add(new PageItemManageEmployeesViewModel(pageItem)); 
         break; 
        default: 
         break; 
       } 
      } 
     } 
    } 

    public class PageItemManageCustomersViewModel : PageViewModelBase 
    { 
     public PageItemManageCustomersViewModel(PageItem pageItem) 
     { 

     } 
    } 

    public class PageItemManageEmployeesViewModel : PageViewModelBase 
    { 
     public PageItemManageEmployeesViewModel(PageItem pageItem) 
     { 


     } 
    } 

    public abstract class ViewModelBase 
    { 
     protected void OnPropertyChanged(string propertyName) 
     { 
      //this is the INotifyPropertyChanged method which all ViewModels need 
     } 
    } 

    public abstract class PageViewModelBase : ViewModelBase 
    { 
     //these are the properties which every PageItemViewModel needs 
     public string IdCode { get; set; } 
     public string Title { get; set; } 
    } 

    public class PageItem 
    { 
     public string IdCode { get; set; } 
     public string Title { get; set; } 
    } 

    public class PageItems 
    { 
     public static List<PageItem> GetAll() 
     { 
      List<PageItem> pageItems = new List<PageItem>(); 
      pageItems.Add(new PageItem { IdCode = "manageCustomers", Title = "ManageCustomers"}); 
      pageItems.Add(new PageItem { IdCode = "manageEmployees", Title = "ManageEmployees"}); 
      return pageItems; 
     } 
    } 

} 
+0

Combien de choses êtes-vous susceptible d'avoir dans le commutateur? Quelle est la probabilité qu'ils soient ajoutés dynamiquement? Pour moi, enlever cela rend difficile la lecture du code pour la maintenance, tout en ne fournissant pas beaucoup en retour. – Paddy

1

Pourquoi ne pouvez-vous mettre une méthode virtuelle GetViewModel() dans votre classe PageItem de base qui retourne le modèle de vue approprié?

foreach (PageItem pageItem in pageItems) 
    { 
     pageItemViewModels.Add(pageItem.GetViewModel()); 
    } 

La chose qui ressemble tout de suite comme une odeur de code est l'utilisation de propriétés « id » - cela peut généralement être remplacé par le polymorphisme. Donc, vous devez remplacer l'instruction switch par le code ci-dessus.

Edit:

Si votre classe PageItem ne sait rien au sujet de votre modèle de vue, il ne peut pas être mis en œuvre de cette façon. Fondamentalement, vous avez besoin d'une usine, que vous avez déjà (d'une certaine manière).

J'ai habituellement une liste de relations (PageItem à ViewModel), qui serait dans votre cas un Dictionary<String, Type>. Ensuite, vous pouvez remplir cette liste lors de l'initialisation et avoir le modèle de vue approprié instancié plus tard.

Pour utiliser la réflexion pour générer cette liste, vous devez au moins savoir par programme quel élément de page est pris en charge par un modèle de vue. A cet effet, vous pouvez utiliser un attribut personnalisé pour décorer vous classe, par exemple .:

public class SupportsPageItemAttribute : Attribute 
{ 
    private readonly string _id; 
    public string ID 
    { 
     get { return _id;} 
    } 

    public SupportsPageItemAttribute(string id) 
    { 
     _id = id; 
    } 
} 

Et puis utilisez cet attribut pour définir qui PageItem votre modèle peut accepter:

[SupportsPageItemAttribute("manageCustomers") 
public class PageItemManageCustomersViewModel 
{ 
    // ... 
} 

Et puis, vous Utilisez la réflexion pour obtenir toutes les classes implémentant IPageItemViewModel et vérifiez leurs attributs pour obtenir la chaîne d'id PageItem.

Par exemple (sans beaucoup d'erreur contrôle):

Dictionary<String, Type> modelsById = new Dictionary<String, Type>(); 
String viewModelInterface = typeof(IPageItemViewModel).FullName; 

// get the assembly 
Assembly assembly = Assembly.GetAssembly(typeof(IPageItemViewModel)); 

// iterate through all types 
foreach (Type viewModel in assembly.GetTypes()) 
{ 
    // get classes which implement IPageItemViewModel 
    if (viewModel.GetInterface(viewModelInterface) != null) 
    { 
     // get the attribute we're interested in 
     foreach (Attribute att in Attribute.GetCustomAttributes(viewModel)) 
     { 
      if (att is SupportsPageItemAttribute) 
      { 
       // get the page item id 
       String id = (att as SupportsPageItemAttribute).ID; 

       // add to dictionary 
       modelsById.Add(id, viewModel); 
      } 
     } 
    } 
} 

D'autre part, il existe plusieurs cadres de inversion contrôle que vous pourriez envisager au lieu de faire la réflexion désagréable travail vous-même.

+0

à droite, mais la classe PageItem est ma classe de modèle qui ne devrait rien savoir de "ViewModels" dans le modèle MVVM, le modèle pourrait également être utilisé par exemple. ASP.NET MVC et ne doit pas renvoyer un "ViewModel". –

1

Une solution possible consiste à inverser la relation entre PageItem et PageItemViewModel dans votre code. En ce moment, vous générez un PageItemViewModel basé sur un PageItem, mais que se passe-t-il si vous avez d'abord créé les PageItemViewModel s puis dans chaque constructeur de PageItemViewModel, vous avez créé le PageItem approprié? Cela élimine le besoin du switch et rend les choses plus propres, car maintenant votre modèle de vue est responsable du modèle, au lieu que le modèle soit responsable du modèle de vue.

Un exemple en fonction de votre code actuel:

using System; 
using System.Collections.Generic; 

namespace TestInstantiate838 
{ 
    public class Program 
    { 
     static void Main(string[] args) 
     { 
      List<ViewModelPageItemBase> pageItemViewModels = PageItemViewModels.GetAll(); 

      // No switch needed anymore. Each PageItem's view-model contains its PageItem 
      // which is exposed as property of the view-model. 
      foreach (ViewModelPageItemBase pageItemViewModel in pageItemViewModels) 
      { 
       System.Console.WriteLine("{0}:{1}", pageItemViewModel.PageItem.IdCode, pageItemViewModel.PageItem.Title); 
      } 
      Console.ReadLine(); 
     } 
    } 

    public class PageItemManageCustomersViewModel : ViewModelPageItemBase 
    { 
     public PageItemManageCustomersViewModel() 
     { 
      PageItem = new PageItem { IdCode = "manageCustomers", Title = "ManageCustomers" }; 
     } 
    } 

    public class PageItemManageEmployeesViewModel : ViewModelPageItemBase 
    { 
     public PageItemManageEmployeesViewModel() 
     { 
      PageItem = new PageItem { IdCode = "manageEmployees", Title = "ManageEmployees" }; 
     } 
    } 

    public abstract class ViewModelPageItemBase : ViewModelBase 
    { 
     //The PageItem associated with this view-model 
     public PageItem PageItem { get; protected set; } 
    } 

    public abstract class ViewModelBase 
    { 
     protected void OnPropertyChanged(string propertyName) 
     { 
      //this is the INotifyPropertyChanged method which all ViewModels need 
     } 
    } 

    public class PageItem 
    { 
     public string IdCode { get; set; } 
     public string Title { get; set; } 
    } 

    // Replaces PageItems class 
    public class PageItemViewModels 
    { 
     // Return a list of PageItemViewModel's instead of PageItem's. 
     // Each PageItemViewModel knows how to build it's corresponding PageItem object. 
     public static List<PageItemViewModelBase> GetAll() 
     { 
      List<PageItemViewModelBase> pageItemViewModels = new List<PageItemViewModelBase>(); 
      pageItemViewModels.Add(new PageItemManageCustomersViewModel()); 
      pageItemViewModels.Add(new PageItemManageEmployeesViewModel()); 
      return pageItemViewModels; 
     } 
    } 
}