Bal*_*ngh 0 c# dependency-injection inversion-of-control mvvm simple-injector
我有一个BaseViewModel由多个ViewModel类继承的.在我,BaseViewModel我有几个依赖注入ViewModel.现在,如果我需要添加一个新的依赖项,我BaseViewModel需要更改所有继承的VM BaseViewModel.请告诉我如何在Simple Injector中处理它.以下是我的代码结构:
如何使我的基类注入独立,以便我不需要在所有继承的类中进行更改?
码:
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)
{
}
}
Run Code Online (Sandbox Code Playgroud)
我的BaseViewModel包含以下一些接口,其实现通过构造函数注入:
- NavigationService
- DataService
- GeoLocationService
- SmartDispatcher
- MessageBus which implement Message Aggregator
Run Code Online (Sandbox Code Playgroud)
它还包含一些常见属性作为静态变量,其数据在整个应用程序中使用,如UserDetails.并且还包含CancellationToken,IsBusy以显示进度条.
BaseViewModel还包含HandleException方法,该方法处理来自所有ViewModel的所有传入异常.还包含一些常用命令,这些命令在所有视图中使用,如Si gnoutCommand,NavigationBar Commands.
实际上它已经开始包含各种ViewModel中使用的各种常用方法.
请建议我如何重构此代码?
你的最后一句话:
实际上它已经开始包含各种ViewModel中使用的各种常用方法
准确描述你的问题!正如Steven所描述的那样,您通过一个基类构建了几乎完整的应用程序.从而侵犯了你现在正在经历的开放原则.
诀窍是围绕非常小的SOLID ViewModels设计应用程序,在运行时组成应用程序.通过拆分ViewModel并使用a UserControl作为您的视图,您可以为用户创建大的复杂视图,同时您仍然可以从使用SOLID设计中获得所有好处.那么让我们来看看你实现的一些不同的接口以及你在基类中"处理"的一些函数:
的NavigationService
这听起来像是一个控制应用程序流程的服务.这听起来像你的主视图(模型).你可以创建一个单独的MainViewModel属性,假设CurrentView您正在使用WPF,通常会将此属性绑定到ContentControl.此控件的内容可以是从单个TextBlock到完整的所有内容UserControl.UserControls仍然可能非常复杂,因为它们可能由多个子用户控件组成,依此类推.使用MVVM框架(例如Caliburn Micro或MVVM Light)可选,但会派上用场.
它也可以是具有某种回调或委托功能的应用程序全局服务,以执行到某个视图(模型)的导航.在任何情况下,应用程序的基础结构部分都应该拥有自己的类,不应该放在基类中.
DataService的
单个数据服务是我工作超过10年的方式.我每次都撞到墙上.有一个时间点,您需要一些特殊的东西,这些东西不包含在您的数据服务中,您可能会通过完整的代码库进行正确的调整.说到开放原则......
比我了解了Command/Handler和Query/Handler模式.你可以在这里和这里读到这个.在所有需要数据的地方使用此模式,您只需注入正确的IQueryHandler <,>并在那里使用它.并非每个视图(模型)都需要数据,当然也不需要相同的数据.那么为什么要使用全局DataService呢?这也将改善您的DBContext对象的生命周期管理.
HandleException
为什么您的基类负责处理viewmodel的异常?基类知道这些异常的含义是什么?基类有什么作用?记录异常,向用户显示一条消息(什么样的消息?)并静默继续?让应用程序在3分钟后崩溃并让用户不知道发生了什么?如果您没有预料到它们会被抛出,那么就不应该捕获IMO异常.然后在应用程序级别(例如在您的应用程序级别Main)记录异常,向用户显示"对不起"消息并关闭应用程序.如果你期望一个例外,那就在那里处理然后处理.
的UserDetails
问自己一个问题,你的40个ViewModel中有多少人确实需要这些信息?如果所有40个人都需要这些信息,那么您的设计还有其他问题.如果没有,只IUserContext在实际使用它们的ViewModels中注入这些细节(甚至更好).
如果您将它用于某种身份验证,请考虑使用包装任务的装饰器,他们需要执行它的权限.
IsBusyIndicator
再说一次:你是否在每个ViewModel中都需要这个?我想不是.我认为此外,向用户显示忙碌指示符是View的责任,而不是ViewModel,并且随着任务的长度确定您是否需要显示此项,请将其作为任务的责任(假设您正在查看您也可以通过使用已经提到的Command/Handler模式以SOLID方式执行任务.
使用WPF,您可以定义一个可以绑定到视图的依赖项属性,从而显示某种繁忙指示符.现在只需注入一个ShowBusyIndicatorService需要显示它的任务.或者将所有(冗长的)任务包装在一个ShowBusyIndicatorDecorator.
设计
现在让我们看一下您可以定义的一些简单接口来构建View(Model).假设我们决定让每个ViewModel负责一个任务,并定义以下(典型的LoB)任务:
可以将单个任务拆分为"显示单个数据类型(实体)的数据".现在我们可以定义以下接口:
IView<TEntity>ISelect<TEntity>IEdit<TEntity>对于每种接口类型,您将根据您的语义首选项创建处理器/服务或DialogHandler,这将执行典型的MVVM操作,例如查找相应的视图并将其绑定到viewmodel并以某种方式显示(模式窗口,将其作为usercontrol注入)在一些内容控制等).
通过在您需要导航或显示不同视图的"父"ViewModel中注入此单个Processor/Service或DialogHandler,您可以通过单行代码显示任何类型的实体,并将职责转移到下一个ViewModel.
我现在在项目中使用这3个接口,我真的可以做我过去可以做的一切,但现在以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>(TEntity entity) where TEntity : class
{
// Compose type
var editEntityViewModelType =
typeof(IEntityEditorViewModel<>).MakeGenericType(entity.GetType());
// Ask S.I. for the corresponding ViewModel,
// which is responsible for editing this type of entity
var editEntityViewModel = (IEntityEditorViewModel<TEntity>)
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<TEntity> where TEntity : class
{
void EditThisEntity(TEntity entity);
}
public class EditUserViewModel : IEntityEditorViewModel<User>
{
public EditUserViewModel(
ICommandHandler<SaveUserCommand> saveUserCommandHandler,
IQueryHandler<GetUserByIdQuery, User> 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; }
}
Run Code Online (Sandbox Code Playgroud)
从您IView<User>现在可以修改与该行代码当前所选的用户:
// Assuming this property is present in IView<User>
public User CurrentSelectedUser { get; set; }
public void EditUser()
{
this.editService.EditEntity(this.CurrentSelectedUser);
}
Run Code Online (Sandbox Code Playgroud)
请注意,通过使用此设计,您可以将ViewModel包装在装饰器中,以执行横切关注,例如日志记录,身份验证等.
所以这是一个很长的答案,最短的答案是:松散基类,它咬你,它会咬你越来越难!
| 归档时间: |
|
| 查看次数: |
1366 次 |
| 最近记录: |