Simple Injector在BaseClass中注入多个依赖项

我有一个BaseViewModel ,它由多个ViewModel类inheritance。 在我的BaseViewModel我有一些从ViewModel注入的依赖项。 现在,如果我需要在BaseViewModel添加新的依赖项,我需要更改inheritanceBaseViewModel所有VM。 请告诉我如何在Simple Injector中处理它。 以下是我的代码结构:

如何使我的基类注入独立,以便我不需要在所有inheritance的类中进行更改?

码:

 public class BaseViewModel { protected readonly IAESEnDecrypt AESEnDecrypt; protected readonly IDataService DataService; protected readonly INavigationService NavigateToPage; public BaseViewModel(INavigationService nav, IDataService data, IAESEnDecrypt encrypt) { AESEnDecrypt= encrypt; NavigateToPage = nav; DataService = data; } } public class ViewModel { public ViewModel(INavigationService nav, IDataService data, IAESEnDecrypt encrypt) : base (nav, data, encrypt) { } } 

我的BaseViewModel包含以下一些接口,其实现通过构造函数注入:

 - NavigationService - DataService - GeoLocationService - SmartDispatcher - MessageBus which implement Message Aggregator 

它还包含一些常见属性作为静态变量,其数据在整个应用程序中使用,如UserDetails。 并且还包含CancellationToken,IsBusy以显示进度条。

BaseViewModel还包含HandleException方法,该方法处理来自所有ViewModel的所有传入exception。 还包含一些常用命令,这些命令在所有视图中使用,如Si gnoutCommand,NavigationBar Commands。

实际上它已经开始包含各种ViewModel中使用的各种常用方法。

请建议我如何重构此代码?

首先要防止这个基类。 这个基类是一个很大的代码气味,结果就是你目前的痛苦。 这样的基类将违反单一责任原则(SRP),并且只是作为所有派生视图模型的大帮助者类,或者甚至看起来你在那里提出了跨领域的关注点。 基类甚至可能隐藏您的视图模型违反SRP的事实。 他们可能做得太多了; 有太多的责任。

相反,尝试执行以下操作:

  • 将横切关注问题从基类转移到装饰器中,或者找到另一种应用横切关注点的方法。
  • 将相关的依赖项组合到一个聚合服务中,并将此类聚合服务注入到视图模型中。

在设计良好的应用程序中,几乎不需要具有依赖性的这种基类。

如果你无法改变你的设计(但请看看它;你将在没有那个基类的情况下更好的地方),你可以恢复显式属性注入。 Simple Injector不会开箱即用,但文档描述了如何执行此操作。

基本上,它归结为编写自定义IPropertySelectionBehavior ,将BaseViewModel的构造函数依赖项移动到公共属性并使用自定义属性标记它们。

但同样,只使用属性注入作为最后的手段。 物业注入只会隐藏设计问题; 它不会解决它。

你的最后一句话:

实际上它已经开始包含各种ViewModel中使用的各种常用方法

准确描述你的问题! 正如史蒂文已经描述的那样,你通过一个基类构建了几乎完整的应用程序。 从而侵犯了您现在正在经历的开放式原则 。

诀窍是围绕非常小的SOLID ViewModels设计应用程序,您可以在运行时编写应用程序。 通过拆分ViewModel并使用UserControl作为视图,您可以为用户创建大的复杂视图,同时您仍然可以从使用SOLID设计中获得所有好处。 那么让我们来看看你实现的一些不同的接口以及你在基类中“处理”的一些函数:

的NavigationService

这听起来像是一个控制应用程序流程的服务。 这听起来像你的主视图(模型)。 你可以创建一个MainViewModel作为单个属性,比如说CurrentView假设你正在使用WPF,你通常会将这个属性绑定到ContentControl。 此控件的内容可以是从单个TextBlock到完整UserControl 。 UserControls仍然可能非常复杂,因为它们可能由多个子用户控件组成,依此类推。 使用MVVM框架(例如Caliburn Micro或MVVM Light )可选,但会派上用场。

它也可以是具有某种回调或委托function的应用程序全局服务,以执行到某个视图(模型)的导航。 在任何情况下,应用程序的基础结构部分都应该拥有自己的类,不应该放在基类中。

DataService的

单个数据服务是我工作超过10年的方式。 我每次都撞到墙上。 有一个时间点,您需要一些特殊的东西,这些东西不包含在您的数据服务中,您可能会通过完整的代码库进行正确的调整。 说到开放原则……

比我了解了Command / Handler和Query / Handler模​​式。 你可以在这里和这里读到这个。 在所有需要数据的地方使用此模式,您只需注入正确的IQueryHandler <,>并在那里使用它。 并非每个视图(模型)都需要数据,当然也不需要相同的数据。 那么为什么要使用全局DataService呢? 这也将改善您的DBContext对象的Lifetime管理。

HandleException

为什么您的基类负责处理viewmodel的exception? 基类知道这些exception的含义是什么? 基类有什么作用? 记录exception,向用户显示消息(什么样的消息?)并静默继续? 让应用程序在3分钟后崩溃并让用户不知道发生了什么? 如果您不希望首先抛出它们,则不应该捕获IMOexception。 然后在应用程序级别(例如在Main )记录exception,向用户显示“对不起”消息并关闭应用程序。 如果您期望exception,请在那里处理,然后根据处理。

的UserDetails

问自己一个问题,你的40个ViewModel中有多少人确实需要这些信息? 如果所有40个人都需要这些信息,那么您的设计还有其他问题。 如果没有,只在实际使用它们的ViewModel中注入这些细节(甚至更好的IUserContext )。

如果您将它用于某种身份validation,请考虑使用包装任务的装饰器,他们需要执行它的权限。

IsBusyIndi​​cator

再说一次:你是否在每个ViewModel中都需要这个? 我想不是。 我认为此外,向用户显示忙碌指示符是View的责任,而不是ViewModel,并且由于任务的长度决定了您是否需要显示此内容,请将其作为任务的责任(假设您正在查看您也可以通过使用已经提到的Command / Handler模​​式以SOLID方式执行任务。

使用WPF,您可以定义可以绑定到视图的依赖项属性 ,从而显示某种繁忙指示符。 现在只需在需要显示它的任务中注入ShowBusyIndicatorService 。 或者将所有(冗长的)任务包装在ShowBusyIndicatorDecorator

设计

现在让我们看一些您可以定义的简单接口来构建View(模型)。 假设我们决定让每个ViewModel负责一个任务,我们定义以下(典型的LoB)任务:

  • 显示(任何类型)数据
  • 选择或选择数据
  • 编辑数据

可以将单个任务拆分为“显示单个数据类型(实体)的数据”。 现在我们可以定义以下接口:

  • IView
  • ISelect
  • IEdit

对于每种接口类型,您将创建一个处理器/服务或DialogHandler,具体取决于您的语义首选项,它可以执行典型的MVVM操作,例如查找相应的视图并将其绑定到viewmodel并以某种方式显示(模式窗口,将其作为usercontrol注入)在一些内容控制等)。

通过在您需要导航或显示不同视图的“父”ViewModel中注入此单个Processor / Service或DialogHandler,您可以通过单行代码显示任何类型的实体,并将职责转移到下一个ViewModel。

我现在在一个项目中使用这三个接口,我真的可以做我过去可以做的一切,但现在以SOLID方式。 我的EditProcessor,接口和viewmodel看起来像这样,从所有不那么有趣的东西中删除。 我正在使用Caliburn Micro进行ViewModel-View绑定。

 public class EditEntityProcessor : IEditEntityProcessor { private readonly Container container; private readonly IWindowManager windowManager; public EditEntityProcessor(Container container, IWindowManager windowManager) { this.container = container; this.windowManager = windowManager; } public void EditEntity(TEntity entity) where TEntity : class { // Compose type var editEntityViewModelType = typeof(IEntityEditorViewModel<>).MakeGenericType(entity.GetType()); // Ask SI for the corresponding ViewModel, // which is responsible for editing this type of entity var editEntityViewModel = (IEntityEditorViewModel) this.container.GetInstance(editEntityViewModelType); // give the viewmodel the entity to be edited editEntityViewModel.EditThisEntity(entity); // Let caliburn find the view and show it to the user this.windowManager.ShowDialog(editEntityViewModel); } } public interface IEntityEditorViewModel where TEntity : class { void EditThisEntity(TEntity entity); } public class EditUserViewModel : IEntityEditorViewModel { public EditUserViewModel( ICommandHandler saveUserCommandHandler, IQueryHandler loadUserQueryHandler) { this.saveUserCommandHandler = saveUserCommandHandler; this.loadUserQueryHandler = loadUserQueryHandler; } public void EditThisEntity(User entity) { // load a fresh copy from the database this.User = this.loadUserQueryHandler.Handle(new GetUserByIdQuery(entity.Id)); } // Bind a button to this method public void EndEdit() { // Save the edited user to the database this.saveUserCommandHandler.Handle(new SaveUserCommand(this.User)); } //Bind different controls (TextBoxes or something) to the properties of the user public User User { get; set; } } 

从您IView您现在可以使用以下代码行编辑当前所选用户:

 // Assuming this property is present in IView public User CurrentSelectedUser { get; set; } public void EditUser() { this.editService.EditEntity(this.CurrentSelectedUser); } 

请注意,通过使用此设计,您可以将ViewModel包装在装饰器中,以执行横切关注,例如日志记录,身份validation等。

所以这是一个很长的答案,简短的答案是:松散基类,它咬你,它会咬你越来越难!

您可以使用ServiceLocator (反)模式使注入独立, 但是您不应该这样做,因为它违反了SOLID的原则 。 Mark Seemann – 服务定位器违反了SOLID

您应该坚持在构造函数中添加依赖项,因为这符合SOLID OO设计原则。