翼度科技»论坛 编程开发 .net 查看内容

深入理解WPF中的依赖注入和控制反转

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
在WPF开发中,依赖注入(Dependency Injection)和控制反转(Inversion of Control)是程序解耦的关键,在当今软件工程中占有举足轻重的地位,两者之间有着密不可分的联系。今天就以一个简单的小例子,简述如何在WPF中实现依赖注入和控制反转,仅供学习分享使用,如有不足之处,还请指正。

 
什么是依赖注入和控制反转?

 
依赖注入又称为依赖项注入,那什么是依赖项呢?比如在一个类A中,实现某中功能,而此功能是另外一个类B实现的,那就说明A依赖B,B就是A的依赖项。或者是另一个对象A所依赖的对象B。示例如下:
  1. namespace DemoIoc
  2. {
  3.     public class MessageWriter
  4.     {
  5.         public void Print(string message)
  6.         {
  7.             Console.WriteLine($"MessageWriter.Write(message: "{message}")");
  8.         }
  9.     }
  10.     public class Worker : BackgroundService
  11.     {
  12.         private readonly MessageWriter writer = new();
  13.         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  14.         {
  15.             while (!stoppingToken.IsCancellationRequested)
  16.             {
  17.                 writer.Print($"Worker running at: {DateTimeOffset.Now}");
  18.                 await Task.Delay(1_000, stoppingToken);
  19.             }
  20.         }
  21.     }
  22. }
复制代码
注意:在上述示例中,Worker类依赖于MessageWriter类,所以MessageWriter就是Worker的依赖项。 硬编码的依赖项(如前面的示例)会产生问题,应避免使用。
强依赖关系具有以下几个问题:

  • 如果要用不同的实现替换 MessageWriter,必须修改 Worker 类。
  • 如果 MessageWriter 具有依赖项,则必须由 Worker 类对其进行配置,且很难进行初始化。
  • 这种实现很难进行单元测试。
那如何解决上述依赖关系所造成的弊端呢?答案就是依赖项注入。可通过如下几个步骤实现:

  • 使用接口或基类将依赖关系实现抽象化。
  • 在服务容器中注册依赖关系。
  • 将服务注入到使用它的类的构造函数中。
 .NET 提供了一个内置的服务容器 IServiceProvider。 服务通常在应用启动时注册,并追加到 IServiceCollection。 添加所有服务后,可以使用 BuildServiceProvider 创建服务容器。 框架负责创建依赖关系的实例,并在不再需要时将其释放。
简单一句话说:依赖注入(DI)将所依赖的对象参数化,接口化,并且将依赖对象的创建和释放剥离出来,这样就做到了解耦,并且实现了控制反转(IoC)
控制反转(IoC)具有如下两个特点:

  • 高等级的代码不能依赖低等级的代码;
  • 抽象接口不能依赖具体实现;
控制反转解决代码的强耦合,增加了代码的可扩展性。依赖注入将依赖具体实现类和控制实现类的创建和释放,变成了依赖接口或抽象类,不再控制接口的创建和释放。两者之间相辅相成,互相成就。

 
WPF中实现依赖注入的步骤

 
1. 安装DI库

 
首先创建一个WPF应用程序,然后在Nuget包管理器中安装微软提供的依赖注入库【Microsoft.Extensions.DependencyInjection】,如下所示:

 
2. 创建接口和实现类

 
创建测试用的接口ITextService和实现类TextService,如下所示:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace DemoIoc
  7. {
  8.     public interface ITextService
  9.     {
  10.        public string GetText();
  11.     }
  12.     public class TextService : ITextService
  13.     {
  14.         public string GetText()
  15.         {
  16.             return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
  17.         }
  18.     }
  19. }
复制代码
 
3. 接口注入

 
在需要调用的地方(如:MainWindow)进行ITextService接口注入,如下所示:
  1. namespace DemoIoc
  2. {
  3.     /// <summary>
  4.     /// Interaction logic for MainWindow.xaml
  5.     /// </summary>
  6.     public partial class MainWindow : Window
  7.     {
  8.         private ITextService textService;
  9.         public MainWindow(ITextService textService)
  10.         {
  11.             this.textService = textService;
  12.             InitializeComponent();
  13.         }
  14.         private void Window_Loaded(object sender, RoutedEventArgs e)
  15.         {
  16.             this.txtCurrentTime.Text = textService.GetText();
  17.         }
  18.     }
  19. }
复制代码
注意:以上可以看出MainWindow依赖ITextService接口,而不依赖于接口的实现。这样就实现了依赖注入。
 
4. 配置容器

 
在启动程序App.xaml.cs中,添加当前对象成员,和服务提供对象,并在实例化服务对象的时候一次性注册,以便在后续需要的时候进行获取。如下所示:
  1. namespace DemoIoc
  2. {
  3.     /// <summary>
  4.     /// Interaction logic for App.xaml
  5.     /// </summary>
  6.     public partial class App : Application
  7.     {
  8.         /// <summary>
  9.         /// 获取当前 App 实例
  10.         /// </summary>
  11.         public new static App Current => (App)Application.Current;
  12.         /// <summary>
  13.         /// 获取存放应用服务的容器
  14.         /// </summary>
  15.         public IServiceProvider ServiceProvider { get; }
  16.         public App()
  17.         {
  18.             ServiceProvider = ConfigureServices();
  19.         }
  20.         /// <summary>
  21.         /// 配置应用的服务
  22.         /// </summary>
  23.         private static IServiceProvider ConfigureServices()
  24.         {
  25.             var serviceCollection = new ServiceCollection()
  26.                 .AddSingleton<ITextService,TextService>()
  27.                 .AddSingleton<MainWindow>();
  28.                
  29.             return serviceCollection.BuildServiceProvider();
  30.         }
  31.         protected override void OnStartup(StartupEventArgs e)
  32.         {
  33.             var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
  34.             mainWindow.Show();
  35.         }
  36.     }
  37. }
复制代码
注意:在此示例中,MainWindow通过服务注册的方式进行实例化,所以需要删除默认的App.xaml中StartUri属性设置,否则将提示默认构造函数不存在。
 
示例测试

 
经过上述步骤,就实现了WPF中依赖注入和控制反转,测试结果如下:

说明:正常输出,则表示依赖注入成功。
 
参考文档

 
1. .Net依赖项注入:https://learn.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection
 
以上就是依赖注入和控制反转的全部内容,希望可以抛砖引玉,一起学习,共同进步。

来源:https://www.cnblogs.com/hsiang/archive/2023/11/04/17806755.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具