依赖注入初级

依赖注入DependencyInjection俗称DI,是控制反转InversionOfControl俗称IOS思想的实现方式,它可以简化模块的过程,降低模块之间的耦合度

一、基本概念与简单例子

  1. 依赖注入的几个概念
    1. 服务(Service):对象
    2. 服务容器:负责管理注册服务
    3. 创建对象以及关联对象
    4. 对象生命周期:Transient(瞬态)、SCocped(范围:同一个指定的作用域获取的是同一个对象,不同作用域它会多新建另外一个对象)、Singleton(单例:无论谁调用都是调用同一个对象)
    5. 服务类型(ServiceType):表示通过什么类型来获得这个服务
    6. 实现类型(ImplementationType):实现服务的类是什么(可以跟服务类型一样,也可以不一样)

2、下面有一个ItestService接口,并且有两个类继承这个接口并对其接口函数进行实现。分别输出中文和英文

public interface ItestService

{

string Name { get; set; }

void SayHi();

}

public class TestServiceImp1:ItestService

{

public string Name { get; set; }

 

public void SayHi()

{

Console.WriteLine(Name + "中文:你好!" );

}

}

public class TestServiceImp2 : ItestService

{

public string Name { get ; set ; }

 

public void SayHi()

{

Console.WriteLine(Name + "English:Hi!");

}

}

}

3、普通方法调用接口类

4、依赖注入方法调用接口类

利用Nuget安装依赖注入包

调用

二、获得对象的生命周期

1、Transient(瞬态):每次获取都会创建一个新的对象。如下t与t2不是同一个对象

  1. SCocped(范围):同一个指定的作用域获取的是同一个对象如下t1与t2是同一个对象
    1. 直接使用服务获得提供者

    1. 先使用服务获得一个Scope,再从Scope中获得对象

 

  1. Singleton:所有范围获得的都是同一个。所以下面t1,t2,t3都是同一个对象

  1. 对于生命周期的选择:
    1. 如果累无状态,建议用Singleton
    2. 如果有状态则建议用Scope
  2. 上面的都是实现类和服务类是一样的

services.AddSingleton<TestServiceImp1>();

t1 = scope1.ServiceProvider.GetService<TestServiceImp1>();

也可以是不一样的,一般用虚基类或接口作为服务类,继承后的类作为实现类,如下:

  1.  
  2.  
  3.  

    二、注册带构造函数参数的类

  1. 以上的服务类型是ItestService,实现类型是TestServiceImp1。获取服务是通过ItestService来获取的。不能通过TestServiceImp1来获取,否则返回的对象为null

  2. 假如仍然是需要通过TestServiceImp1来获得服务,可以把服务类型和实现类型都设置为TestServiceImp1

    三、通过基层接口获取多个服务

TestServiceImp1和TestServiceImp2都继承与接口ItestService,则通过ItestService可以获得这它所有已经注册的实现类对象

    四、依赖注入综合例1

.net的DI(依赖注入)默认是构造函数注入。它是有"传染性"的,即,假如A构造函数中需要B,B构造函数中需要C。那么它内部会像递归一样,找到最低层C然后先构造C。如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI创建。但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值

例:现需要实现

  1. 通过人名从数据库中找出对应的IP地址
  2. 向IP地址发送一段信息,并记录执行过程
  1. 新建一个用于写记录文件的接口

    interface LogTool

    {

    void WriteLog(string msg);

    }

    class LogClass : LogTool

    {

    public LogClass()

    {

     

    }

    public void WriteLog(string msg)

    {

    Console.WriteLine($"日志:{msg}");

    }

    }

  2. 新建一个用于由姓名从数据库中获得IP地址的接口(这里需要通过构造函数传入一个写记录文件的对象)

    interface GetIpAdressTool

    {

    string ReadFileFromDB(string name);

    }

    class GetIpAdressClass : GetIpAdressTool

    {

    private readonly LogTool mLogTool;

    public GetIpAdressClass(LogTool mLogTool) {

    this.mLogTool = mLogTool;

    }

    public string ReadFileFromDB(string name)

    {

    mLogTool.WriteLog("开始读取IP地址");

    string Ret= "192.168.1.1";

    mLogTool.WriteLog("读取成功");

    return Ret;

    }

    }

  3. 新建一个用于联网发送数据的接口(这里需要从构造函数中传入一个由姓名获取IP地址的对象)

    interface SendTool

    {

    void Send(string content, string name);

    }

    class SendClass : SendTool

    {

    private readonly GetIpAdressTool mGetIpAdressTool;

    public SendClass(GetIpAdressTool mGetIpAdressTool)

    {

    this.mGetIpAdressTool = mGetIpAdressTool;

    }

    public void Send(string content, string name)

    {

    string server = mGetIpAdressTool.ReadFileFromDB(name);

    Console.WriteLine($"向服务器:{server}的文件名为{name}上传{content}");

    }

}

  1. 新建一个总控制类(需要从构造函数中传入发送对象与文件记录对象)

    class Controller

    {

    private readonly LogTool mLogTool;

    private readonly SendTool mSendTool;

    public Controller(LogTool mLogTool, SendTool mSendTool)

    {

    this.mLogTool = mLogTool;

    this.mSendTool = mSendTool;

    }

    public void Test()

    {

    this.mLogTool.WriteLog("开始上传");

    this.mSendTool.Send("你好", "hh");

    this.mLogTool.WriteLog("上传完成");

    }

}

  1. 注册服务:把所有需要用到的类添加到依赖注入的服务数组中。

ServiceCollection service = new ServiceCollection();

service.AddScoped<Controller>();

service.AddScoped<LogTool, LogClass>();

service.AddScoped<SendTool, SendClass>();

service.AddScoped<GetIpAdressTool, GetIpAdressClass>();

  1. 使用

using (var sp = service.BuildServiceProvider())

{

var c = sp.GetRequiredService<Controller>();

c.Test();

}

Console.ReadKey();

  1. 效果:

  1. 上述实现了一个综合的依赖注入,是通过构造函数栈的顺序来注入的。类与类之间互相多次使用(比如上面的文件记录的类LogTool),程序员只管把所有服务添加进去,在使用时框架会先自动通过构造函数构造出对象,提供使用。而不需要管创建每个类的对象。下图是在调用var c = sp.GetRequiredService<Controller>();获得服务时,框架自动按一下顺序调用构造函数。(按构造函数的栈顺序来调用,最底层的最先调用)

    注:GetIpAdressTool的构造函数中有LogTool对象,Controller的构造函数中也有LogTool对象,在框架自动调用构造函数时,只会调用一次构造函数

    五、改进1:修改读取配置方式

上述的读取配置是从注册表或环境变量中读取,没有传入参数。现需改成传入一个文件路径,从这个文件路径下读取。

  1. 新增一个从文件读取类,文件路径外部传入

class GetIpAdressClassFromFile : GetIpAdressTool

{

public string FilePath { get; set; }

public GetIpAdressClassFromFile(string mPath )

{

this. FilePath = mPath;

}

public string ReadFileFromDB(string name)

{

string Ret =$"从文件{FilePath}读取192.168.1.1";

return Ret;

}

}

  1. 把原来的

    service.AddScoped<GetIpAdressTool, GetIpAdressClass>();

    替换成

    service.AddScoped(typeof(GetIpAdressTool), s => new GetIpAdressClassFromFile("c:/hh.123" ));

 

    六、改进2:扩展方法修改AddScope

上面的AddScope都是单独通过AddScope添加到ServiceCollection中的,下面通过扩展方法,把它改进添加到ServiceCollection类中。

ServiceCollection service = new ServiceCollection();

service.AddScoped<Controller>();

service.AddScoped<LogTool, LogClass>();

service.AddScoped<SendTool, SendClass>();

1、新建类,把namespace改成与ServiceCollection类一样。添加IserviceCollection类的扩展方法

namespace Microsoft.Extensions.DependencyInjection

{

public static class ConsoleLogExtensions

{

public static void AddConsoleLog(this IServiceCollection services)

{

services.AddScoped<传染性DI.Program.LogClass>();

}

}

}

2、后面可以把

service.AddScoped<LogTool, LogClass>();

替换成

service.AddConsoleLog();

 

 

 

 

 

 

 

posted @ 2022-09-13 17:46  ihh2021  阅读(69)  评论(0编辑  收藏  举报