如何模拟多重继承并使用反射来优化此代码?
-
06-07-2019 - |
题
我有一个 WPF 应用程序,其中 页面项目 是模型对象。
我的主 ViewModel 有一个 ObservableCollection 页面项目视图模型, ,每个都从其匹配的 PageItem 模型对象构建自身。
每个 页面项目视图模型 继承自抽象类 基础视图模型 为了获得 INotifyPropertyChanged 功能。
每个 页面项目视图模型 还实施了 IPageItemView模型 以确保它具有所需的属性。
我最终会有大约 50 页,所以我想 消除任何不必要的代码:
- 已解决(见下文):有没有办法让 PageItemViewModel 类 继承IdCode和Title 所以我不必在每个班级中实施它们?我无法将它们放入 BaseViewModel 中,因为其他 ViewModel 继承了它,不需要这些属性,并且我无法将它们放入 IPageItemViewModel 中,因为它只是一个接口。我明白我需要 多重继承 对于这个 C# 不支持的
- 已解决(见下文):有什么办法可以摆脱 转变 声明,例如以某种方式使用 反射 反而?
下面是一个 独立控制台应用程序 这演示了我的代码 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;
}
}
}
重构:接口改为抽象类
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;
}
}
}
消除 Switch 语句的答案:
谢谢贾布:
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);
解决方案
一种不太漂亮但有效的解决方案是使用约定来摆脱 switch 语句。这假设您可以更改 IdCodes 或至少修改大小写以匹配 ViewModel。
var type = Type.GetType("PageItem" + pageItem.IdCode + "ViewModel");
var viewModel = Activator.CreateInstance(type) as ViewModelBase;
pageItemViewModels.Add(viewModel);
请注意,您应该在此处添加错误检查,这里有几个失败点。然而,这比必须维护不断增长的 switch 语句要好。
其他提示
您是否可以创建一个继承自 BaseViewModel 的类来实现这两个属性 - 然后您需要此属性的 PageItemViewModel 类可以继承该类。
按照 Paddy 的建议,我刚刚创建了一个额外的抽象类 PageViewModelBase,并定义了这些自动属性:
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;
}
}
}
为什么你不能放一个 GetViewModel()
您的 PageItem 基类中的虚拟方法返回适当的视图模型?
foreach (PageItem pageItem in pageItems)
{
pageItemViewModels.Add(pageItem.GetViewModel());
}
立即看起来像代码味道的事情是“id”属性的使用 - 这通常可以用多态性代替。所以你会替换 switch
声明与上面的代码。
编辑:
如果您的 PageItem 类对您的视图模型一无所知,则不能以这种方式实现。基本上,你需要一个工厂,你已经拥有了(在某种程度上)。
我通常有一个关系列表(PageItem 到 ViewModel),在您的情况下是一个 Dictionary<String, Type>
. 。然后,您可以在初始化期间填充此列表,并在稍后实例化正确的视图模型。
要使用反射来构建此列表,您至少需要以编程方式了解视图模型支持哪个页面项。为此,您可以使用自定义属性来装饰您的类,例如:
public class SupportsPageItemAttribute : Attribute
{
private readonly string _id;
public string ID
{
get { return _id;}
}
public SupportsPageItemAttribute(string id)
{
_id = id;
}
}
然后使用该属性来定义您的模型可以接受哪个 PageItem:
[SupportsPageItemAttribute("manageCustomers")
public class PageItemManageCustomersViewModel
{
// ...
}
然后,您使用反射来获取所有实现 IPageItemViewModel 的类并检查它们的属性以获取 PageItem id 字符串。
例如(没有太多错误检查):
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);
}
}
}
}
另一方面,您可以考虑使用各种控制反转框架,而不是自己进行讨厌的反射工作。
一种可能的解决方案是颠倒之间的关系 PageItem
和 PageItemViewModel
在你的代码中。现在,您正在生成一个 PageItemViewModel
基于一个 PageItem
, ,但是如果你创建了 PageItemViewModel
首先是 s,然后是每个 PageItemViewModel
的构造函数,您创建了适当的 PageItem
?这消除了对 switch
并使事情变得更清晰,因为现在你的视图模型负责模型,而不是模型负责视图模型。
基于您当前代码的示例:
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;
}
}
}