WPF MVVM中如何扩展视图DataContext的抽象类实现切换逻辑
解决WPF MVVM任务创建违反开闭原则的方案
你的核心问题是用switch硬编码任务实例创建逻辑,导致新增TaskBase子类必须修改AddTaskViewModel,违反开闭原则。下面给你几个落地的优化方案,都是WPF MVVM场景下常用的:
方案一:注册表式工厂模式(最推荐)
把任务类型和对应的创建逻辑抽离到专门的工厂类里,用字典维护类型映射,AddTaskViewModel只依赖工厂接口,不关心具体创建细节。
步骤1:定义工厂接口与实现
// 抽象工厂接口,只负责创建任务实例 public interface ITaskFactory { TaskBase CreateTask(TaskType taskType); } // 具体工厂实现,维护任务类型到创建函数的映射 public class TaskFactory : ITaskFactory { private readonly Dictionary<TaskType, Func<TaskBase>> _taskCreators; public TaskFactory() { // 初始化现有任务类型的创建逻辑 _taskCreators = new Dictionary<TaskType, Func<TaskBase>> { { TaskType.ToDo, () => new ToDoTask() }, { TaskType.Reminder, () => new Reminder() } }; } public TaskBase CreateTask(TaskType taskType) { if (_taskCreators.TryGetValue(taskType, out var creator)) { return creator(); } throw new ArgumentOutOfRangeException(nameof(taskType), "不支持该任务类型"); } }
步骤2:改造AddTaskViewModel
通过构造函数注入工厂,移除switch逻辑:
public class AddTaskViewModel : ViewModelBase { private readonly ITaskFactory _taskFactory; private TaskType _selectedTaskType; private TaskBase _currentTask; public TaskType SelectedTaskType { get => _selectedTaskType; set { if (SetProperty(ref _selectedTaskType, value)) { // 直接调用工厂创建实例,无需switch CurrentTask = _taskFactory.CreateTask(value); } } } public TaskBase CurrentTask { get => _currentTask; set => SetProperty(ref _currentTask, value); } public AddTaskViewModel(ITaskFactory taskFactory) { _taskFactory = taskFactory; // 设置默认任务类型 SelectedTaskType = TaskType.ToDo; } }
新增任务类型时的操作
比如新增RecurringTask子类,只需要在TaskFactory的字典里加一行:
{ TaskType.Recurring, () => new RecurringTask() }
完全不用修改AddTaskViewModel的代码,完美符合开闭原则。
进阶优化:结合依赖注入自动注册
如果用Microsoft DI或Autofac等容器,可以把每个任务的创建逻辑注册为服务,让工厂自动收集映射,连TaskFactory都不用改:
// 在DI容器注册时 services.AddSingleton<ToDoTask>(); services.AddSingleton(new KeyValuePair<TaskType, Func<TaskBase>>( TaskType.ToDo, sp => sp.GetRequiredService<ToDoTask>())); services.AddSingleton<Reminder>(); services.AddSingleton(new KeyValuePair<TaskType, Func<TaskBase>>( TaskType.Reminder, sp => sp.GetRequiredService<Reminder>())); // 新增RecurringTask时只需要加这两段注册代码
然后修改TaskFactory的构造函数:
public TaskFactory(IEnumerable<KeyValuePair<TaskType, Func<TaskBase>>> creators) { _taskCreators = creators.ToDictionary(kv => kv.Key, kv => kv.Value); }
方案二:反射+特性标记(自动化程度更高)
如果任务类型较多,不想手动维护注册表,可以用特性标记任务类,通过反射自动扫描生成映射:
步骤1:定义特性类
[AttributeUsage(AttributeTargets.Class)] public class TaskTypeMetadataAttribute : Attribute { public TaskType TaskType { get; } public TaskTypeMetadataAttribute(TaskType taskType) { TaskType = taskType; } }
步骤2:给任务类加特性
[TaskTypeMetadata(TaskType.ToDo)] public class ToDoTask : TaskBase { /* ... */ } [TaskTypeMetadata(TaskType.Reminder)] public class Reminder : TaskBase { /* ... */ }
步骤3:改造TaskFactory
public class TaskFactory : ITaskFactory { private readonly Dictionary<TaskType, Func<TaskBase>> _taskCreators; public TaskFactory() { _taskCreators = AppDomain.CurrentDomain.GetAssemblies() // 只扫描自己的程序集,避免性能问题 .Where(a => a.GetName().Name == "你的程序集名称") .SelectMany(a => a.GetTypes()) // 筛选TaskBase的非抽象子类 .Where(t => typeof(TaskBase).IsAssignableFrom(t) && !t.IsAbstract) .Select(t => new { TaskType = t.GetCustomAttribute<TaskTypeMetadataAttribute>()?.TaskType, Creator = (Func<TaskBase>)(() => (TaskBase)Activator.CreateInstance(t)) }) .Where(x => x.TaskType.HasValue) .ToDictionary(x => x.TaskType.Value, x => x.Creator); } public TaskBase CreateTask(TaskType taskType) { if (_taskCreators.TryGetValue(taskType, out var creator)) { return creator(); } throw new ArgumentOutOfRangeException(nameof(taskType), "不支持该任务类型"); } }
这样新增任务类型时,只需要创建子类并加上[TaskTypeMetadata]特性,其他代码完全不用改,自动化拉满。
配合WPF视图的无缝衔接
你现在用ContentControl绑定TaskBase展示视图,新增任务类型时只需要在XAML里添加对应的DataTemplate:
<Window.Resources> <DataTemplate DataType="{x:Type local:ToDoTask}"> <local:ToDoTaskView /> </DataTemplate> <DataTemplate DataType="{x:Type local:Reminder}"> <local:ReminderView /> </DataTemplate> <!-- 新增RecurringTask时加这段 --> <DataTemplate DataType="{x:Type local:RecurringTask}"> <local:RecurringTaskView /> </DataTemplate> </Window.Resources>
ContentControl会自动根据TaskBase的实际类型渲染对应的视图,和上面的工厂方案完美配合。
内容的提问来源于stack exchange,提问作者Melissa
相关产品推荐
相关产品推荐

