由于项目比较忙,所以很久没有学习Mvvm工具包了。
那为什么突然又学了呢,因为项目中遇到了一个问题——不同的ViewModel间该如何通信?
不过我并不确定Ioc与这个问题有什么关系,只是感觉可能有关系。在学之前,我稍微搜了下Ioc是什么,看到了它的一个齿轮模型,觉得很有意思,所以还是偷了个闲来学习了。
我先来解释一下Ioc是啥,然后再去学习官方文档的东西。
Ioc是Inversion Of Control的缩写,直译过来就是控制反转。
这是什么奇怪的名字。
从一个齿轮例子说起,
为什么是齿轮呢?可能是大家都喜欢造轮子吧。
如果把程序看成一个现实中运转的机械系统,那你写的代码模块就像系统中转动的齿轮一样。
假设这三个齿轮组成了一个系统,显然A齿轮的转动会带动小齿轮B、C。
如果把该齿轮系统看作程序,这说明什么?
说明A和B、C之间存在依赖关系。再稍微专业一点就是,模块间存在耦合。
要使整个系统运转起来,就要一个齿轮转动来带动其他齿轮转动。先转动的那个齿轮掌握着主动权,即控制权。
模块间存在耦合不可避免,但实际开发中,我们往往更喜欢低耦合,这就得解耦。
所以我们能不能使这几个齿轮分开,达到解耦呢?
如果直接分开,一个齿轮转动就带动不了其他齿轮了,那系统就运转不起来了。
现在,我们可以在这几个齿轮之间再加入一个齿轮。
所以现在是A转动带动D,再由D带动B和C?
如果是这样,那还不如A直接带动B和C呢。
新加入的齿轮D其实是第三方的东西,在你的程序里你可以认为它是一个框架提供的机制或功能。
它才是一直在转动的一方。
D一直在转,你需要A时,就把A装到D上去,需要B时就把B装上去。这样你需要的功能就会跟着“转起来”。(要啥装啥,且不需要三者直接相关了,这样A、B、C之间不就解耦了么?不过这只是一个粗略的理解,具体怎样还得往后看)
对比第一张图的情况,原来是由你的齿轮先转动,带动其他几个齿轮。
现在你的齿轮都是被动的一方,都是由这个第三方的齿轮所带动的。
到这里,控制反转的意思应该呼之欲出了。主动权(或者说控制权)发生了转变。你由主动方变成了被动方。
有了上面的基本理解之后,来正式学习下Ioc。
Ioc是一种常见的模式,用来增强(使用MVVM模式的)程序的模块性。
增加模块化程度的,并且是适用于使用MVVM的程序的。不要啥程序都Ioc。
Ioc最常见的实现方式是用依赖注入(DI,Dependency Injection),实现思路是创建大量的服务,并将服务注入到后端类中(如,作为参数传给ViewModel构造函数)——这使得使用这些服务的代码不再依赖于服务的实现细节,并能很容易替换服务的具体实现。
使用DI,模块间不再直接依赖,它们之间的代码不再耦合在一起。你更换一侧服务的细节实现,那调用侧并不用做更改,因为对它来说,我还是调用这个服务,至于内部发生了什么变化并不关心。
这种模式还可以很容易地将平台特有的特性提供给后端代码,方法是通过服务抽象它们,然后注入到需要的地方。
MVVM工具包不提供内置的API来促进该模式使用,因为已经存在针对这种模式的专用库了,比如 Microsoft.Extensions.DependencyInjection 包,该包提供了一个功能齐全、功能强大的DI API集,并通过一个易于安装和使用的工具—— IServiceProvider 起作用。下面介绍会引用该库,并提供一系列示例来说明如何将它集成到MVVM模式的程序中。
API集:Ioc
第一步是声明一个 IServiceProvider 实例,并初始化所有需要的服务,通常是在程序启动时做这些事。例如,在UWP上(类似设置也可用于其他框架):
public sealed partial class App : Application
{
public App()
{
Services = ConfigureServices();
this.InitializeComponent();
}
///
/// Gets the current instance in use
///
public new static App Current => (App)Application.Current;
///
/// Gets the instance to resolve application services.
///
public IServiceProvider Services { get; }
///
/// Configures the services for the application.
///
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<IFilesService, FilesService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IClipboardService, ClipboardService>();
services.AddSingleton<IShareService, ShareService>();
services.AddSingleton<IEmailService, EmailService>();
return services.BuildServiceProvider();
}
}
本例中,Services 属性在启动时被初始化,并且所有的应用程序服务和viewmodel都被注册。还有一个新的 Current 属性,该属性可用于从程序的其他view中轻松访问 Services 属性。例如:
IFilesService filesService = App.Current.Services.GetService<IFilesService>();
// Use the files service here...
这里关键的点是,每个服务都能很好地使用特定于平台的API,但由于这些API都是通过我们代码使用的接口抽象出来的,在解析实例和用它执行操作时,所以我们不需要关心它们。
还有一个强大的特性是“构造函数注入(constructor injection)”,这意味着DI(依赖注入)服务提供程序能在创建被请求类的实例时自动解析已注册服务之间的间接依赖关系。考虑以下服务:
public class FileLogger : IFileLogger
{
private readonly IFilesService FileService;
private readonly IConsoleService ConsoleService;
public FileLogger(
IFilesService fileService,
IConsoleService consoleService)
{
FileService = fileService;
ConsoleService = consoleService;
}
// Methods for the IFileLogger interface here...
}
这里我们有一个实现了 IFileLogger 接口的 FileLogger 类,并且需要 IFilesService 和 IConsoleService 实例。构造函数注入意味着DI服务提供程序会自动收集所有必要的服务,就像这样:
///
/// Configures the services for the application.
///
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<IFilesService, FilesService>();
services.AddSingleton<IConsoleService, ConsoleService>();
services.AddSingleton<IFileLogger, FileLogger>();
return services.BuildServiceProvider();
}
// Retrieve a logger service with constructor injection
IFileLogger fileLogger = App.Current.Services.GetService<IFileLogger>();
DI服务提供程序会自动检查是否已经注册所有需要的服务,然后会检索它们并调用已注册的 IFileLogger 具体类的构造函数,来获取返回的实例。
服务提供程序的名称中有“服务”两字,但实际上,它可以用来解析任何类的实例,包括ViewModel!上文提到的概念仍然适用,包括构造函数注入。假设我们有一个ContactsViewModel 类,通过它的构造函数使用一个 IContactsService 和 IPhoneService 实例。我们可以用一个 ConfigureServices 方法,就像这样:
///
/// Configures the services for the application.
///
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
// Services
services.AddSingleton<IContactsService, ContactsService>();
services.AddSingleton<IPhoneService, PhoneService>();
// Viewmodels
services.AddTransient<ContactsViewModel>();
return services.BuildServiceProvider();
}
接着在 ContactsView 中,分配如下的数据上下文:
public ContactsView()
{
this.InitializeComponent();
this.DataContext = App.Current.Services.GetService<ContactsViewModel>();
}
本文中应用型的内容不多,几个基本用法的示例很难让你对(Ioc)DI的应用场景及具体用法有较清晰的认识。
因为在MVVM Toolkit的官方文档里对DI的描述就是这么简略(这本来就是讲Ioc的章节),想要更深入了解DI的应用,得看DI的专门章节。
所以我觉得本文把Ioc的概念给弄懂就好,并且几个基本示例可以再结合网上别人的例子看看具体如何使用,不至于之后正式用时感到陌生。