作为视图的DataContext在抽象类实现之间切换WPF MVVM

azpvetkf  于 2023-05-08  发布在  其他
关注(0)|答案(1)|浏览(150)

我正在构建一个WPF MVVM应用程序,它代表一个任务管理器->添加任务并跟踪它们是否完成。
我有一个抽象类TaskBase和两个示例任务-> ToDoTaskReminder,它们都继承自TaskBase,但有一些更多的属性。
我已经实现了一个ITaskRepository,它保留了所有的任务,并具有获取,添加,编辑和删除任务等功能。
该应用程序的主要思想是显示任务,并有可能添加一个任务。每当用户单击Add按钮时,就会出现一个新窗口,其中包含一个ComboBox,它绑定到一个枚举TaskType(当前有2个值- Reminder和ToDoTask),一个ContentControl,它绑定到一个TaskBase类型的属性,其数据应该被填充,以及一个IRelayCommand,它将任务添加到存储库。
目视检查:x1c 0d1x

public class AddTaskViewModel : ObservableObject
{
    private TaskType selectedTaskType;
    private TaskBase task;

    public TaskType SelectedTaskType
    {
        get => selectedTaskType;
        set
        {
            SetProperty(ref selectedTaskType, value);

            switch (selectedTaskType)
            {
                case TaskType.ToDoTask:
                    Task = new ToDoTask();
                    break;
                case TaskType.Reminder:
                    Task = new Reminder();
                    break;
            }
        }
    }

    public TaskBase Task 
    { 
        get => task;
        set => SetProperty(ref task, value); 
    }

    public IRelayCommand AddTaskCommand { get; set; }
}

AddTaskViewModelMainViewModel内部的属性,AddTaskCommand实际上是在那里设置的。
AddTaskView.xaml中,ContentControlDataContext设置为Task,其样式具有触发器,因此每当用户更改所选任务类型时,ContentControl的内容就会相应更改。
一切都工作得很好,做了它应该做的事情,但我关心的是这部分代码:

public TaskType SelectedTaskType
{
    get => selectedTaskType;
    set
    {
        SetProperty(ref selectedTaskType, value);

        switch (selectedTaskType)
        {
            case TaskType.ToDoTask:
                Task = new ToDoTask();
                break;
            case TaskType.Reminder:
                Task = new Reminder();
                break;
        }
    }
}

我想让所有东西都尽可能地可扩展,但是如果我添加另一个类,从TaskBase继承,并希望更快地使所有东西适应它,我还必须修改这个开关的情况(打破开放/封闭原则)。
在构建应用程序架构时,是否有更好的方法?

up9lanfz

up9lanfz1#

问题是您是基于参数创建示例的。这总是需要一种参数检查。
此模式引入了两个问题:1) 对具体类型和实现细节(如构造函数参数)的显式依赖;2) 参数类型switch。这两种方法都损害了可扩展性。
问题 1) 通过将示例化代码移出依赖于这些新示例的类型来解决。
通常使用 Factory Method 模式或 Abstract Factory 模式。这意味着示例化被移动到类型本身或工厂。
这并不能解决问题 2):现在负责动态示例化的另一类型仍然必须检查其参数,以便决定示例化哪个类型。
问题 2) 通过引入一种哈希表来解决。

  • Abstract Factory* 模式是最好的,因为它避免了在应用程序中传递哈希表的需要。

例如,将这两种解决方案组合成TaskFactory可以如下所示:

interface ITaskFactory
{
  // Does not throw if creation fails
  bool TryCreate(TaskType taskType, out TaskBase newTask);

  // Throws if creation fails
  TaskBase Create(TaskType taskType);
}

class TaskFactory : ITaskFactory
{
  public TaskFactory(IReadOnlyDictionary<TaskType, Func<TaskBase>> productFactoryTable) 
    => this.ProductFactoryTable = productPool;

  public TaskBase Create(TaskType taskType) 
  {
    Func<TaskBase> taskFactory = this.ProductFactoryTable[taskType];
    return taskFactory.Invoke();
  }

  public bool TryCreate(TaskType taskType, out TaskBase newTask) 
  {
    bool canCreate = this.ProductFactoryTable.TryGetValue(taskType, out Func<TaskBase> taskFactory);
    newTask = taskFactory?.Invoke();
    return canCreate;
  }

  private IReadOnlyDictionary<TaskType, Func<TaskBase>> ProductFactoryTable { get; }
}

您可以直接使用这个类,也可以将它隐藏在现有的ITaskRepository后面。因为已经有了一个专门的仓库类,我将扩展它的API以允许创建TaskBase示例:

ITaskRepository.cs

interface ITaskRepository 
{
  TaskBase CreateNewTask(TaskType taskType);
  
  /* Existing API */
}

任务库.cs

class TaskRepository : ITaskRepository
{
  private ITaskFactory TaskFactory { get; }

  public TaskRepository(ITaskFactory taskFactory)
  {
    this.TaskFactory = taskFactory;
  }

  public TaskBase CreateNewTask(TaskType taskType)
    => this.TaskFactory.Create(taskType);

  /* Existing API implementation */
}

使用示例

AddTaskViewModel.cs

class AddTaskViewModel : INotifyPropertyChanged
{
  public AddTaskViewModel(ITaskRepository taskRepository)
  {
    this.TaskRepository = taskRepository;
  }

  private void OnSelectedTaskTypeChanged()
    => this.SelectedTask = this.TaskRepository.CreateNewTask(this.SelectedTaskType);

  public ITaskRepository TaskRepository { get; }
  public TaskBase SelectedTask { get; set; }

  private TaskType selectedTaskType;
  public TaskType SelectedTaskType
  {
    get => this.selectedTaskType;
    set
    {
      this.selectedTaskType = value;
      OnPropertyChanged();
      OnSelectedTaskTypeChanged();
    }
  }
}

App.xaml.cs

public partial class App : Application
{
  private void OnApplicationStarting(object sender, StartupEventArgs e)
  {
    // This is the only place that will require modification 
    // when new TaskBase implementations are added 
    // (of course the TaskType enum and the UI will require modification too)
    var productFactoryTable = new Dictionary<TaskType, Func<TaskBase>>()
    {
      { TaskType.Reminder, () => new Reminder() },
      { TaskType.ToDoTask, () => new ToDoTask() },
    };
    var taskFactory = new TaskFactory(productFactoryTable)
    var taskRepository = new TaskRepository(taskfactory);
    var addTaskViewModel = new AddTaskViewModel(taskRepository);
    var mainViewModel = new ManViewModel(addTaskViewModel);
    var mainWindow = new MainWindow(mainViewModel);

    mainWindow.Show();
  }
}

相关问题