找回密码
 立即注册
快捷导航

[.NET] 【杨中科】32-39 学习笔记

[复制链接]
妙笔生花 2023-9-5 05:17:58 | 显示全部楼层

32、依赖注入DI/控制反转IoC

生活中的“控制反转”:自己买发电机发电和用电网的电。

依赖注入(Dependency Injection,DI)是控制反转(Inversion of Control,IOC)思想的实现方式。

依赖注入简化模块的组装过程,降低模块之间的耦合度

自己发电的代码

var connSettings = ConfigurationManager.ConnectionStrings["connStr1"];
string connStr = connSettings.ConnectionString;SqlConnection conn = new SqlConnection(connStr);

代码控制反转的目的

从“怎样创建XX对象” 到 “我要XX对象” 的两种实现方式:

1) 服务定位器(ServiceLocator);
2) 依赖注入(Dependency Injection,DI);

服务定位器

IDbConnection conn = ServiceLocator.GetService<IDbConnection>();

依赖注入

// (伪代码)
class Demo{
    public IDbConnection Conn { get; set; }
    public void InsertDB()
    {
        IDbCommand cmd = Conn.CreateCommand();
    }
}

33、依赖注入2 NET中DI服务注册

DI几个概念

服务(service):就是你要的 对象;

注册服务;就是 创建对象

服务容器:负责管理注册的服务;

查询服务:创建对象及关联对象;

对象生命周期:Transient(瞬态); Scoped(范围); Singleton(单例);

.NET 中使用DI

1、测试代码见备注

public interface ITestService{
    public string Name{get;set;}
    public void SayHi();
}
public class TestServicelmpl: ITestService{
    public string Name{get;set;}
    public void SayHi() { Console.WriteLine($"Hi, I'm {Name}");}
}
public class TestServicelmpl2: ITestService{
    public string Name{get;set;}
    public void SayHi() { Console.WriteLine($"你好,我是 {Name}");}
}

// 正常使用
ITestService t = new TestServicelmpl();
t.Name="xiaoming";
t.SayHi();

2、根据类型来获取和注册服务。可以分别指定 服务类型(service type)实现类型(implementation type)。这两者可能相同,也可能不同。服务类型可以是类,也可以是接口,建议面向接口编程,更灵活。

上面代码中,服务类型为 ITestService,实现类型为 TestServicelmpl

3、.NET控制反转组件取名为DependencyInjection,但它包含ServiceLocator的功能。

安装控制反转的组件:

1、Install-Package Microsoft.Extensions.DependencyInjection

2、using Microsoft.Extensions.DependencyInjection;

3、ServiceCollection 用来构造容器对象 IServiceProvider 。调用 ServiceCollection的BuildServiceProvider() 创建的 ServiceProvider,可以用来获取 BuildServiceProvider() 之前 ServiceCollection 中的对象。

static void Mian(string[] args){
    // 构造容器对象 services
    ServiceCollection services = new ServiceCollection();
    // 注册一个瞬态的服务,服务类型和实现类型都是 TestServicelmpl
    services.AddTransient<TestServicelmpl>();
    // 这个相当于服务定位器
    using(ServiceProvider serviceProvider = services.BuildServiceProvider()){
        // 从服务器容器中获取TestServicelmpl服务
        TestServicelmpl testService = serviceProvider.GetService<TestServicelmpl>();
        testService.Name = "tom";
        testService.SayHi();
    }
}

// 这代码总的来说就是:创建 服务容器,将 服务 注册到 服务容器 中,然后再从服务容器获取指定服务出来执行,,,

34、依赖注入3 服务生命周期

1、给类构造函数中打印,看看不同生命周期的对象创建,使用serviceProvider.CreateScope()创建Scope。

// 构造容器对象 services
ServiceCollection services = new ServiceCollection();

// 注册一个瞬态的服务,服务类型和实现类型都是 TestServicelmpl
//services.AddTransient<TestServicelmpl>();
//services.AddSingleton<TestServicelmpl>();
services.AddScoped<TestServicelmpl>();

// 这个相当于服务定位器
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
    // 从服务器容器中获取TestServicelmpl服务
    TestServicelmpl testService = serviceProvider.GetService<TestServicelmpl>();
    TestServicelmpl testService2 = serviceProvider.GetService<TestServicelmpl>();

    Console.WriteLine("testService 和 testService2:" + object.ReferenceEquals(testService, testService2));

    // Scope 作用域
    using (IServiceScope scope1 = serviceProvider.CreateScope()){
        // 在 scope 中获取Scope相关对象,即 scope1.ServiceProvider 而不是 serviceProvider
        var scopeS1 = scope1.ServiceProvider.GetService<TestServicelmpl>();
        var scopeS2 = scope1.ServiceProvider.GetService<TestServicelmpl>();

        Console.WriteLine("scopeS1 和 scopeS2:" + object.ReferenceEquals(scopeS1, scopeS2));
        Console.WriteLine("testService 和 scopeS2:" + object.ReferenceEquals(testService, scopeS2));
    }
}

2、如果一个类实现了IDisposable接口,则离开作用域之后容器会自动调用对象的Dispose方法。

3、不要在长生命周期的对象中引用比它短的生命周期的对象。在ASP.NET Core中,这样做默认会抛异常。

4、生命周期的选择:如果类无状态,建议为Singleton;如果类有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都是运行在同一个线程中的,没有并发修改的问题;在使用Transient的时候要谨慎。

5、.NET注册服务 的重载方法很多,看着文档琢磨吧。

35、依赖注入4 服务定位器

其他注册方法:

  • 服务类型和实现类型不一致的注册
  • 简单看看其他Add*方法
// 注册一个瞬态的服务,服务类型和实现类型都是 TestServicelmpl
services.AddTransient<TestServicelmpl>();
services.AddTransient<ITestService, TestServicelmpl>();
services.AddTransient(typeof(ITestService), typeof(TestServicelmpl));
services.AddTransient(typeof(ITestService), new TestServicelmpl());

IServiceProvider的服务定位器方法:

  • T GetService<T>() 如果获取不到对象,则返回null。
  • object GetService(Type serviceType)
  • T GetRequiredService<T>() 如果获取不到对象,则抛异常
  • object GetRequiredService(Type serviceType)
  • IEnumerable<T> GetServices<T>() 适用于可能有很多满足条件的服务
  • IEnumerable<object> GetServices(Type serviceType)

36、依赖注入5 NET依赖注入

DI魅力渐显:依赖注入

1、依赖注入是有“传染性”的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值;但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值。

2、.NET的DI默认是构造函数注入。

3、案例。

37-39、依赖注入6 DI综合案例1-3

需求说明

1、目的:演示DI的能力;

2、有配置服务、日志服务,然后再开发一个邮件发送器服务。可以通过配置服务来从文件、环境变量、数据库等地方读取配置,可以通过日志服务来将程序运行过程中的日志信息写入文件、控制台、数据库等。

3、说明:案例中开发了自己的日志、配置等接口,这只是在揭示原理,.NET有现成的,后面讲。

实现1

1、创建四个.NET Core类库项目,ConfigServices是配置服务的项目,LogServices是日志服务的项目,MailServices是邮件发送器的项目,然后再建一个.NET Core控制台项目MailServicesConsole来调用MailServices。MailServices项目引用ConfigServices项目和LogServices项目,而MailServicesConsole项目引用MailServices项目。

2、编写类库项目LogServices,创建ILogProvider接口。编写实现类ConsoleLogProvider。编写一个ConsoleLogProviderExtensions定义扩展方法AddConsoleLog,namespace和IServiceCollection 一致。

实现2

1、编写配置服务的类库项目ConfigServices。接口IConfigProvider,方法:string GetValue(string name)。

2、环境变量读取配置类EnvVarConfigProvider:Environment.GetEnvironmentVariable(name);编写一个类带扩展方法:AddEnvVarConfig

3、编写从ini文件中读取配置的类ConfigServices。

实现3

1、“可覆盖的配置读取器”。配置中心服务器。可以本地的覆盖配置服务器的,或者配置文件覆盖环境变量的。例如,按照“配置中心服务器”、“本地环境变量”、“本地配置文件”的顺序添加了三个配置提供者,在“配置中心服务器”中提供了“a=1;b=2;c=3”这三个配置项,在“本地环境变量”中配置了“a=10;b=20;”,在“本地配置文件”中配置了“b=200”,那么最终我们读取的时候读到的就是“a=10;b=200;c=3;”

2、定义一个从各个ConfigProvider中读取项的IConfigReader接口。编写实现类LayeredConfigReader 。

实现4

1、编写发送邮件的服务。IMailSender接口,实现类DefaultMailSender。注入IConfigReader ,ILogProvider ,不真的发邮件,想真的发邮件用MailKit。

2、整合、调试。

总结

关注于接口,而不是关注于实现,各个服务可以更弱耦合的协同工作。在编写代码的时候,我们甚至都不知道具体的服务是什么。

第三方DI容器:Autofac等。Autofac优点:支持属性注入、基于名字注入、基于约定的注入等。

如无必要,勿增实体

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

温馨提示

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

Archiver|手机版|小黑屋|DLSite

GMT+8, 2024-11-22 16:43

Powered by Discuz! X3.5 and PHP8

快速回复 返回顶部 返回列表