具有 ViewModel 依赖项的 ICommand
ICommand with ViewModel dependency
我正在寻找一种模式,以便在我使用 ICommand 时在我的应用程序中保持 SOLID 原则。基本上我的问题是命令执行与视图模型有依赖关系,但同时视图模型与命令有依赖关系(我通过构造函数注入它们)。我只想保留带有属性的视图模型,所以这是我当前实现的示例:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel()
{
}
public void SetCommand(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readOnly MyViewModel myViewModel;
public MyViewModelCommandManager(MyViewModel myViewModel)
{
this.myViewModel = myViewModel;
}
public ExectueMyCommand()
{
MessageBox.Show(this.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModel viewModel = new MyViewModel();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("viewModel", viewModel) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
viewModel.SetCommand(myCommand);
return viewModel;
}
}
所以,要避免使用SetCommand方法。我想过两种解决方案,但我不知道它们是否优雅。
第一个是将视图模型依赖从构造函数移动到更新代码的方法,这样:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand(MyViewModel viewModel);
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
public MyViewModelCommandManager()
{
....
}
public ExectueMyCommand(MyViewModel viewModel)
{
MessageBox.Show(myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(..);
ICommand myCommand = new DelegateCommand<MyViewModel>(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
return viewModel;
}
}
当然,xaml代码会使用CommandParameter:
<Button Content="Show Message" Command="{Binding MyCommand}" CommandParameter="{Binding .}" />
我想到的其他解决方案是使用一个技巧来创建 viewModel 的包装器,并且 commandManager 与包装器而不是 viewModel 有依赖关系:
internal class MyViewModelCommandContext
{
public MyViewModel ViewModel { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyViewModelCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModelCommandContext context = new MyViewModelCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
context.ViewModel = viewModel;
return viewModel;
}
}
我认为第一个是这个问题的最佳解决方案,你认为什么是最佳解决方案。您会应用其他解决方案吗?
恕我直言,这两种解决方案都过于复杂。 SOLID 很棒,KISS 更好
您的 MyViewModelCommandManager
目前直接耦合到 MyViewModel
,因为它需要后者的 Message
,那么将它们分开有什么好处?为什么不直接在 MyViewModel
?
中执行命令
如果这需要向 MyViewModel
中注入过多的依赖项,那么请考虑您实际需要命令执行的操作,并抽象出不需要的所有其他内容。
- 该命令显示一条消息。
- 该消息由
MyViewModel
持有
- 您想在
MyViewModel
外显示消息(也许其他viewmodels也需要显示消息而您想重用代码?)
- 因此,您真正需要的只是来自
MyViewModel
的某种通知,它想要显示消息,或者发生了导致显示消息的事情。
可能的解决方案:
- 将
IMessageDisplayService
注入 MyViewModel
。 MyViewModel
用消息调用它。
- 向
MyViewModel
注入一个类似于上面的回调。
MyViewModel
引发一个事件,并将消息作为 EventArg。
上述解决方案的推断责任略有不同。
- 表示
MyViewModel
负责。它想要显示一条消息。
- 不太明确。
MyViewModel
知道它需要调用回调,但并不真正知道或关心它做了什么。
- 类似于 2,但更加解耦。多个事物可以订阅或取消订阅该事件,但
MyViewModel
仍然幸福无知。
所有这三个都意味着显示消息的东西不需要知道 MyViewModel
。你已经把它们解耦了。是 MyViewModelFactory
完成所需的任何接线工作。
感谢您的意见。
当你说我正在创建一个复杂的模式时,我理解你,但是在一个拥有大型开发团队的大项目中,如果没有清晰的模式和职责分离,代码维护可能无法执行。
阅读您和您的第三个解决方案后,我想到了一个可能的解决方案。这看起来很复杂,但在我看来,它提高了代码质量。我将创建一个 commandContext,它只具有代码所需的视图模型属性,避免在命令管理器中拥有所有视图模型。此外,我将创建一个 class ,其职责是在视图模型更改时保持上下文更新。这是可能的代码:
internal class MyCommandContext
{
public string Message { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public string OtherProperty { get; set; }
public ObservableCollection<MyChildViewModel> Childs { get; set; }
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.Message);
}
}
internal interface IMyViewModelCommandSynchronizer
{
void Initialize();
}
internal class MyViewModelCommandSynchronizer : IMyViewModelCommandSynchronizer, IDisposable
{
private readOnly MyViewModel viewModel;
private readOnly MyCommandContext context;
MyViewModelCommandSynchronizer(MyViewModel viewModel, MyCommandContext context)
{
this.viewModel = viewModel;
this.context = context;
}
public void Initialize()
{
this.viewModel.PropertyChanged += this.ViewModelOnPropertyChanged;
}
private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Message")
{
this.context.Message = this.viewModel.Message;
}
}
// Dispose code to deattach the events.
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyCommandContext context = new MyCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
IMyViewModelCommandSynchronizer synchronizer = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context), new ParameterOverride("viewModel", viewModel) });
synchronizer.Initialize();
return viewModel;
}
}
我正在寻找一种模式,以便在我使用 ICommand 时在我的应用程序中保持 SOLID 原则。基本上我的问题是命令执行与视图模型有依赖关系,但同时视图模型与命令有依赖关系(我通过构造函数注入它们)。我只想保留带有属性的视图模型,所以这是我当前实现的示例:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel()
{
}
public void SetCommand(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readOnly MyViewModel myViewModel;
public MyViewModelCommandManager(MyViewModel myViewModel)
{
this.myViewModel = myViewModel;
}
public ExectueMyCommand()
{
MessageBox.Show(this.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModel viewModel = new MyViewModel();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("viewModel", viewModel) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
viewModel.SetCommand(myCommand);
return viewModel;
}
}
所以,要避免使用SetCommand方法。我想过两种解决方案,但我不知道它们是否优雅。
第一个是将视图模型依赖从构造函数移动到更新代码的方法,这样:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand(MyViewModel viewModel);
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
public MyViewModelCommandManager()
{
....
}
public ExectueMyCommand(MyViewModel viewModel)
{
MessageBox.Show(myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(..);
ICommand myCommand = new DelegateCommand<MyViewModel>(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
return viewModel;
}
}
当然,xaml代码会使用CommandParameter:
<Button Content="Show Message" Command="{Binding MyCommand}" CommandParameter="{Binding .}" />
我想到的其他解决方案是使用一个技巧来创建 viewModel 的包装器,并且 commandManager 与包装器而不是 viewModel 有依赖关系:
internal class MyViewModelCommandContext
{
public MyViewModel ViewModel { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyViewModelCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModelCommandContext context = new MyViewModelCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
context.ViewModel = viewModel;
return viewModel;
}
}
我认为第一个是这个问题的最佳解决方案,你认为什么是最佳解决方案。您会应用其他解决方案吗?
恕我直言,这两种解决方案都过于复杂。 SOLID 很棒,KISS 更好
您的 MyViewModelCommandManager
目前直接耦合到 MyViewModel
,因为它需要后者的 Message
,那么将它们分开有什么好处?为什么不直接在 MyViewModel
?
如果这需要向 MyViewModel
中注入过多的依赖项,那么请考虑您实际需要命令执行的操作,并抽象出不需要的所有其他内容。
- 该命令显示一条消息。
- 该消息由
MyViewModel
持有
- 您想在
MyViewModel
外显示消息(也许其他viewmodels也需要显示消息而您想重用代码?) - 因此,您真正需要的只是来自
MyViewModel
的某种通知,它想要显示消息,或者发生了导致显示消息的事情。
可能的解决方案:
- 将
IMessageDisplayService
注入MyViewModel
。MyViewModel
用消息调用它。 - 向
MyViewModel
注入一个类似于上面的回调。 MyViewModel
引发一个事件,并将消息作为 EventArg。
上述解决方案的推断责任略有不同。
- 表示
MyViewModel
负责。它想要显示一条消息。 - 不太明确。
MyViewModel
知道它需要调用回调,但并不真正知道或关心它做了什么。 - 类似于 2,但更加解耦。多个事物可以订阅或取消订阅该事件,但
MyViewModel
仍然幸福无知。
所有这三个都意味着显示消息的东西不需要知道 MyViewModel
。你已经把它们解耦了。是 MyViewModelFactory
完成所需的任何接线工作。
感谢您的意见。
当你说我正在创建一个复杂的模式时,我理解你,但是在一个拥有大型开发团队的大项目中,如果没有清晰的模式和职责分离,代码维护可能无法执行。
阅读您和您的第三个解决方案后,我想到了一个可能的解决方案。这看起来很复杂,但在我看来,它提高了代码质量。我将创建一个 commandContext,它只具有代码所需的视图模型属性,避免在命令管理器中拥有所有视图模型。此外,我将创建一个 class ,其职责是在视图模型更改时保持上下文更新。这是可能的代码:
internal class MyCommandContext
{
public string Message { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public string OtherProperty { get; set; }
public ObservableCollection<MyChildViewModel> Childs { get; set; }
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.Message);
}
}
internal interface IMyViewModelCommandSynchronizer
{
void Initialize();
}
internal class MyViewModelCommandSynchronizer : IMyViewModelCommandSynchronizer, IDisposable
{
private readOnly MyViewModel viewModel;
private readOnly MyCommandContext context;
MyViewModelCommandSynchronizer(MyViewModel viewModel, MyCommandContext context)
{
this.viewModel = viewModel;
this.context = context;
}
public void Initialize()
{
this.viewModel.PropertyChanged += this.ViewModelOnPropertyChanged;
}
private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Message")
{
this.context.Message = this.viewModel.Message;
}
}
// Dispose code to deattach the events.
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyCommandContext context = new MyCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
IMyViewModelCommandSynchronizer synchronizer = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context), new ParameterOverride("viewModel", viewModel) });
synchronizer.Initialize();
return viewModel;
}
}