3.1.1 什么是控制反转、依赖注入
杨老师在书中进行了一系列的文字阐述,总结一下就是:软件设计模式中有一种叫做【控制反转】的设计模式,而依赖注入是实现这种设计模式的一个很重要的方式。也就是说学习依赖注入,是学习怎样实现控制反转这一设计模式。
1. 控制反转
控制反转是一种编程思想,它将对象的创建、依赖关系的管理等控制权从代码内部转移到外部容器。在传统编程中,对象的创建和依赖关系的处理是在代码中硬编码实现的,这会导致代码之间的耦合度较高。而控制反转通过将这些控制权交给外部容器,使得代码更加灵活和可扩展。
实现方式:
控制反转主要有两种实现方式:依赖注入(Dependency Injection,DI)和依赖查找(Dependency Lookup)。
2. 依赖注入
依赖注入是控制反转最常用的实现方式,它是指将对象的依赖关系通过构造函数、属性或者方法参数的方式注入到对象中。常见的依赖注入有三种:
2.1 构造函数注入
cs
// 定义一个接口
public interface IService
{
void DoSomething();
}
// 实现接口
public class Service : IService
{
public void DoSomething()
{
Console.WriteLine("Doing something...");
}
}
// 依赖于 IService 的类
public class Client
{
private readonly IService _service;
// 通过构造函数注入依赖
public Client(IService service)
{
_service = service;
}
public void Execute()
{
_service.DoSomething();
}
}
// 使用示例
class Program
{
static void Main()
{
IService service = new Service();
Client client = new Client(service);
client.Execute();
}
}
Client
类通过构造函数接收一个 IService
类型的对象,这样 Client
类就不负责创建 IService
对象,而是由外部负责创建并注入,实现了依赖的反转。
2.2 属性注入
cs
// 定义一个接口
public interface IService
{
void DoSomething();
}
// 实现接口
public class Service : IService
{
public void DoSomething()
{
Console.WriteLine("Doing something...");
}
}
// 依赖于 IService 的类
public class Client
{
public IService Service { get; set; }
public void Execute()
{
if (Service != null)
{
Service.DoSomething();
}
}
}
// 使用示例
class Program
{
static void Main()
{
IService service = new Service();
Client client = new Client();
client.Service = service;
client.Execute();
}
}
在属性注入中,Client
类通过公共属性 Service
接收依赖对象。
2.3 方法注入
cs
// 定义一个接口
public interface IService
{
void DoSomething();
}
// 实现接口
public class Service : IService
{
public void DoSomething()
{
Console.WriteLine("Doing something...");
}
}
// 依赖于 IService 的类
public class Client
{
public void Execute(IService service)
{
service.DoSomething();
}
}
// 使用示例
class Program
{
static void Main()
{
IService service = new Service();
Client client = new Client();
client.Execute(service);
}
}
方法注入是指在调用方法时将依赖对象作为参数传递给方法。
3.1.2 .NET Core 依赖注入的基本使用
原著讲:依赖注入框架中注册的服务有一个重要的概念叫做"生命周期",通俗的说就是"获取服务的时候是创建一个新对象还是用之前的对象"。依赖注入框架中服务的生命周期有三种:
1. 瞬态
每次请求创建新对象。避免多段代码共用一个对象造成混乱,缺点耗资源。
2. 范威
在给定范围内,多次请求共享同一个服务对象,服务每次请求的时候都会返回同一个对象。
3. 单例
全局共享一个服务对象。为了避免并发修改问题,单例的服务对象最好是无状态对象。
杨老师建议选择:
如果一个类没有状态,建议把服务的生命周期设置为单例。
如果有状态且框架环境中有范围控制,建议设置为范围,通常在范围控制下代码都在同一线程,所以没有并发修改问题。
在使用瞬态时,尽量在子范围中使用,以免控制不当造成内存泄漏。
3.1.3 依赖注入的魅力所在

书上举得例子代码不太完善,但杨老师在视频教程中演示了一个例子,比较简介明了,我实操了一下:
当然,在使用依赖注入之前还需要安装Microsoft.Extensions.DependencyInjection

cs
using Microsoft.Extensions.DependencyInjection;
using System.Runtime.CompilerServices;
class Program
{
static void Main()
{
// 创建一个服务容器实例,用于注册服务
ServiceCollection services1 = new ServiceCollection();
// 注册Controller类为作用域服务
services1.AddScoped<Controller>();
// 注册ILog接口的实现类LogImpl为作用域服务
services1.AddScoped<ILog, LogImpl>();
// 注册IStorage接口的实现类StorgeImpl为作用域服务
services1.AddScoped<IStorage, StorgeImpl>();
// 注册IConfig接口的实现类ConfigImpl为作用域服务
services1.AddScoped<IConfig, ConfigImpl>();
// 构建服务提供者,用于解析服务
using (var sp = services1.BuildServiceProvider())
{
// 从服务提供者中获取Controller实例
var c = sp.GetRequiredService<Controller>();
// 调用Controller的Test方法
c.Test();
}
}
}
// Controller类用于处理业务逻辑
class Controller
{
private readonly ILog log;
private readonly IStorage storage;
// 构造函数,通过依赖注入接收ILog和IStorage接口的实现类
public Controller(ILog ig, IStorage IS)
{
this.log = ig;
this.storage = IS;
}
// Test方法用于测试日志记录和文件存储功能
public void Test()
{
this.log.Log("开始上传");
this.storage.Save("bufweqrijfhyurjfnkjbhs", "1998.txt");
this.log.Log("上传完毕!!!");
}
}
// ILog接口定义日志记录方法
interface ILog
{
public void Log(string msg);
}
// LogImpl类实现ILog接口,用于向控制台输出日志信息
class LogImpl : ILog
{
public void Log(string msg)
{
Console.WriteLine($"日志:{msg}");
}
}
// IConfig接口定义获取配置值方法
interface IConfig
{
public string GetValue(string name);
}
// ConfigImpl类实现IConfig接口,用于返回固定的配置值
class ConfigImpl : IConfig
{
public string GetValue(string name)
{
return "OKOKOKK";
}
}
// IStorage接口定义文件保存方法
interface IStorage
{
public void Save(string content, string name);
}
// StorgeImpl类实现IStorage接口,用于根据配置信息保存文件内容到指定服务器
class StorgeImpl : IStorage
{
private readonly IConfig config;
// 构造函数,通过依赖注入接收IConfig接口的实现类
public StorgeImpl(IConfig CG)
{
this.config = CG;
}
// Save方法用于根据配置信息保存文件内容到指定服务器
public void Save(string content, string name)
{
string service = config.GetValue("server");
Console.WriteLine($"服务器:{service}, {content}:{name}");
}
}
杨老师在这个例子里讲到依赖注入的核心奥义就是:降低各个模块之间的耦合度。再补充描述一下:这种依赖注入的模式极大的提高了项目的可维护性和各个模块的可插拔性。
比如例子当中获取数据存储的配置参数,当配置参数的实现或者类型变化时,只需要改变【IConfig 】 的实现即可,业务层的【StorgeImpl 】完全不用管【IConfig config 】是怎么实现或者怎么变化的,只要是【IConfig】对象即可。

服务的实现和服务的调用高度解耦,真正意义上实现了且超级简易的实现了**"我创建对象"到"我要对象"**的改变。