我的问题基于微软的InventorySampleApp。
ServiceLocator
包含注册Services和ViewModels的方法Configure()
。用方法GetService<T>()
可以得到它,例如ProductView.cs
:
ViewModel = ServiceLocator.Current.GetService<ProductDetailsViewModel>();
每个*ViewModel
都包含带接口的构造函数,例如:
public ProductDetailsViewModel(IProductService productService, IFilePickerService filePickerService, ICommonServices commonServices)
我无法理解ViewModel用来将此类接口放入其构造函数的magiс。所以没有这样的线路:
... = new ProductDetailsViewModel(productService, filePickerService, commonServices)
ViewModel构造函数如何获得所需的接口?
服务定位器
public class ServiceLocator : IDisposable
{
static private readonly ConcurrentDictionary<int, ServiceLocator> _serviceLocators = new ConcurrentDictionary<int, ServiceLocator>();
static private ServiceProvider _rootServiceProvider = null;
static public void Configure(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<ISettingsService, SettingsService>();
serviceCollection.AddSingleton<IDataServiceFactory, DataServiceFactory>();
serviceCollection.AddSingleton<ILookupTables, LookupTables>();
serviceCollection.AddSingleton<ICustomerService, CustomerService>();
serviceCollection.AddSingleton<IOrderService, OrderService>();
serviceCollection.AddSingleton<IOrderItemService, OrderItemService>();
serviceCollection.AddSingleton<IProductService, ProductService>();
serviceCollection.AddSingleton<IMessageService, MessageService>();
serviceCollection.AddSingleton<ILogService, LogService>();
serviceCollection.AddSingleton<IDialogService, DialogService>();
serviceCollection.AddSingleton<IFilePickerService, FilePickerService>();
serviceCollection.AddSingleton<ILoginService, LoginService>();
serviceCollection.AddScoped<IContextService, ContextService>();
serviceCollection.AddScoped<INavigationService, NavigationService>();
serviceCollection.AddScoped<ICommonServices, CommonServices>();
serviceCollection.AddTransient<LoginViewModel>();
serviceCollection.AddTransient<ShellViewModel>();
serviceCollection.AddTransient<MainShellViewModel>();
serviceCollection.AddTransient<DashboardViewModel>();
serviceCollection.AddTransient<CustomersViewModel>();
serviceCollection.AddTransient<CustomerDetailsViewModel>();
serviceCollection.AddTransient<OrdersViewModel>();
serviceCollection.AddTransient<OrderDetailsViewModel>();
serviceCollection.AddTransient<OrderDetailsWithItemsViewModel>();
serviceCollection.AddTransient<OrderItemsViewModel>();
serviceCollection.AddTransient<OrderItemDetailsViewModel>();
serviceCollection.AddTransient<ProductsViewModel>();
serviceCollection.AddTransient<ProductDetailsViewModel>();
serviceCollection.AddTransient<AppLogsViewModel>();
serviceCollection.AddTransient<SettingsViewModel>();
serviceCollection.AddTransient<ValidateConnectionViewModel>();
serviceCollection.AddTransient<CreateDatabaseViewModel>();
_rootServiceProvider = serviceCollection.BuildServiceProvider();
}
static public ServiceLocator Current
{
get
{
int currentViewId = ApplicationView.GetForCurrentView().Id;
return _serviceLocators.GetOrAdd(currentViewId, key => new ServiceLocator());
}
}
static public void DisposeCurrent()
{
int currentViewId = ApplicationView.GetForCurrentView().Id;
if (_serviceLocators.TryRemove(currentViewId, out ServiceLocator current))
{
current.Dispose();
}
}
private IServiceScope _serviceScope = null;
private ServiceLocator()
{
_serviceScope = _rootServiceProvider.CreateScope();
}
public T GetService<T>()
{
return GetService<T>(true);
}
public T GetService<T>(bool isRequired)
{
if (isRequired)
{
return _serviceScope.ServiceProvider.GetRequiredService<T>();
}
return _serviceScope.ServiceProvider.GetService<T>();
}
#region Dispose
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_serviceScope != null)
{
_serviceScope.Dispose();
}
}
}
#endregion
使用依赖注入时,对象的实例化会移动到名为依赖注入(DI(容器或控制反转(IoC(容器的组件中。该组件具有某种注册表,其中包含可以实例化的所有已知服务。在您的示例中,serviceCollection
就是该注册表。
现在,每当组件A
需要注册表中的实例时,都有两个不同的选项:
- 直接向容器请求一个实例,例如
ServiceLocator.Current.GetService<ProductDetailsViewModel>()
。这被称为服务定位器模式(我建议立即忘记这一点( - 与其直接询问容器,不如通过
A
(例如public A(ProductDetailsViewModel viewModel)
(的构造函数来请求依赖关系
第二种方法可以越来越向上推,直到到达应用程序层次结构的顶部,即所谓的composition root
。
无论如何,在这两种方式中,容器都使用反射机制。这是一种检索类、方法、属性、构造函数等元数据的方法。每当容器被要求某个类型(例如ProductDetailsViewModel
(时,他都会使用反射来获取有关其构造函数的信息
一旦构造函数被解析,它的依赖关系也是已知的(IProductService
、IFilePickerService
、ICommonServices
(。由于这些依赖关系是在容器中注册的(记住serviceCollection
(,因此可以创建实例
这种情况一直持续下去,直到不再有依赖项,容器可以开始实例化和组合所有对象。最后,给出了ProductDetailsViewModel
的一个实例。如果构建链中有一个容器未知的依赖项,则实例化失败。
因此,基本上,实例化过程从代码转移到DI容器中。
GetService
方法调用ServiceProvider。GetService。这是一种库方法,可以为您处理事务。在封面之下,它使用反射来检查您请求的类型的构造函数。
例如,当您请求ProductDetailsViewModel
时,ServiceLocator
可以看到它需要类型为IProductService
、IFilePickerService
和ICommonServices
的对象。
然后,它查看其服务注册表。例如,线路
serviceCollection.AddSingleton<IProductService, ProductService>();
针对接口IProductService
注册具体类型ProductService
,因此每当ServiceLocator
需要创建IProductService
对象时,它都会使用ProductService
对象。
这个过程被称为自动布线,在Steven van Deursen的第12章和我的关于依赖注入的书中有更详细的描述。
我无法理解ViewModel用于将此类接口放入其构造函数的magiс。
事实上,帮自己一个忙,学习Pure DI,而不是依赖于你觉得不舒服的不透明库。
我以前从未见过这个示例代码库,但从这里发布的示例来看,它看起来充满了代码气味和反模式。