依赖注入设计模式速查手册

一、核心概念

术语 说明
依赖注入(DI) 由外部负责创建和注入对象所需的依赖,而非对象自己 new 依赖实例
控制反转(IoC) 对象创建和生命周期管理的控制权从调用方反转到外部,DI 是 IoC 的一种实现方式
服务(Service) 被注入的依赖对象,通常通过接口抽象暴露能力
容器(Container) 负责管理服务注册、解析和生命周期的中间件(如 IServiceProvider
构造函数注入 通过构造函数参数传入依赖,最常用、最推荐的注入方式
属性注入 通过公共 setter 属性注入依赖,适用于可选依赖
方法注入 通过方法参数注入依赖,适用于一次性使用的场景
生命周期(Lifetime) 服务实例的存活时间:Transient(瞬态)、Scoped(作用域)、Singleton(单例)

二、常用操作

常用方式速查

方式 关键代码 适用场景
构造函数注入 public Cls(IDep dep) { _dep = dep; } 必需依赖,最推荐
属性注入 public IDep Dep { get; set; } 可选依赖
手动注册服务 services.AddTransient<IFoo, Foo>() .NET 内置容器
手动解析服务 serviceProvider.GetRequiredService<IFoo>() 从容器获取实例

1. 构造函数注入(最常用)

cs 复制代码
// 定义服务接口
public interface ICustomerRepository
{
    List<Customer> GetList();
    bool Add(Customer model);
}
​
// 实现服务
public class CustomerRepository : ICustomerRepository
{
    public List<Customer> GetList() => new List<Customer>();
    public bool Add(Customer model) => true;
}
​
// 通过构造函数注入依赖 ------ ViewModel 不再自己 new Repository
public class CustomerViewModel
{
    private readonly ICustomerRepository _repository;
​
    public CustomerViewModel(ICustomerRepository repository)
    {
        _repository = repository; // 外部注入,解耦
    }
​
    public void LoadData()
    {
        var customers = _repository.GetList();
    }
}

2. 在 MVVM 项目中的实际应用

cs 复制代码
// 银行系统中的 LoginViewModel ------ 通过构造函数注入 Repository 和 Window
public class LoginViewModel : ViewModelBase
{
    private readonly UserInfoRepository repository;
    private readonly Window loginWindow;
​
    // 构造函数注入:依赖从外部传入,ViewModel 不关心具体创建方式
    public LoginViewModel(UserInfoRepository repository, Window loginWindow)
    {
        this.repository = repository;
        this.loginWindow = loginWindow;
    }
​
    public RelayCommand LoginCommand
    {
        get => new RelayCommand((obj) =>
        {
            // 数据校验
            if (string.IsNullOrWhiteSpace(Account))
            {
                MessageBox.Show("账号不能为空!");
                return;
            }
​
            if (string.IsNullOrWhiteSpace(Password))
            {
                MessageBox.Show("密码不能为空!");
                return;
            }
​
            // 登录逻辑
            var exp = Expressionable.Create<ViewUserInfo>();
            exp.And(it => it.Account == Account);
            exp.And(it => it.Password == Password);
            exp.And(it => it.Status == 0);
            var list = repository.GetList(exp);
​
            if (list.Count > 1)
            {
                MessageBox.Show("账号重复!");
                return;
            }
            else if (list.Count < 1)
            {
                MessageBox.Show("账号或密码有误!");
                return;
            }
            else
            {
                LoginInfo.CurrentUser = list[0];
                loginWindow.DialogResult = true;
            }
        });
    }
}
​
// 使用时手动注入依赖(传统 WPF 项目无 DI 容器的写法)
var repository = new UserInfoRepository();
var viewModel = new LoginViewModel(repository, this);
this.DataContext = viewModel;

3. 常见注入方式对比

注入方式 适用场景 优点 缺点
构造函数注入 必需依赖 强制依赖明确、支持不可变字段 构造函数参数过多时臃肿
属性注入 可选依赖 灵活、不强制 依赖可能为 null,运行时才发现缺失
方法注入 一次性使用 作用域明确 不适合长期持有的依赖

4. 服务生命周期

生命周期 说明 典型场景
Transient(瞬态) 每次请求创建新实例 无状态服务、轻量级操作
Scoped(作用域) 同一作用域内共享实例 数据库 DbContext、请求级缓存
Singleton(单例) 全局唯一实例 配置服务、日志服务、缓存服务
cs 复制代码
// .NET 内置 DI 容器注册示例
public void ConfigureServices(IServiceCollection services)
{
    // 瞬态:每次注入都是新实例
    services.AddTransient<ICustomerRepository, CustomerRepository>();
​
    // 作用域:同一次请求内共享
    services.AddScoped<AppDbContext>();
​
    // 单例:全局唯一
    services.AddSingleton<ILogService, LogService>();
}

5. 手动实现简易 DI 容器

cs 复制代码
// 不依赖第三方框架,手写一个简易的服务容器
public class SimpleContainer
{
    private readonly Dictionary<Type, Func<object>> _registrations = new();
​
    // 注册服务:接口 -> 实现工厂
    public void Register<TInterface, TImplementation>()
        where TImplementation : TInterface, new()
    {
        _registrations[typeof(TInterface)] = () => new TImplementation();
    }
​
    // 注册单例
    public void RegisterSingleton<TInterface, TImplementation>()
        where TImplementation : TInterface, new()
    {
        var instance = default(TImplementation);
        _registrations[typeof(TInterface)] = () =>
        {
            instance ??= new TImplementation();
            return instance;
        };
    }
​
    // 解析服务
    public T Resolve<T>()
    {
        if (_registrations.TryGetValue(typeof(T), out var factory))
            return (T)factory();
        throw new InvalidOperationException($"服务 {typeof(T).Name} 未注册");
    }
}
​
// 使用示例
var container = new SimpleContainer();
container.Register<IUserRepository, UserRepository>();
var repo = container.Resolve<IUserRepository>();

三、问题排查

错误1:循环依赖(Circular Dependency)

  • 现象:构造函数注入时栈溢出或容器抛出异常

  • 原因:A 依赖 B,B 又依赖 A,形成死循环

  • 解决:提取共同依赖到第三个服务 C;或使用属性注入打破循环

cs 复制代码
// 错误:A 依赖 B,B 依赖 A
public class ServiceA { public ServiceA(ServiceB b) { } }
public class ServiceB { public ServiceB(ServiceA a) { } }
​
// 正确:提取共同逻辑到 ServiceC
public class ServiceC { /* 共同逻辑 */ }
public class ServiceA { public ServiceA(ServiceC c) { } }
public class ServiceB { public ServiceB(ServiceC c) { } }

错误2:未注册服务

  • 现象InvalidOperationException: No service for type 'XXX' has been registered

  • 原因:容器中未注册该服务类型

  • 解决 :在 ConfigureServices 中添加 services.AddTransient/Scoped/Singleton

错误3:生命周期不匹配

  • 现象:Scoped 服务被 Singleton 依赖时抛出异常或数据混乱

  • 原因:Singleton 生命周期长于 Scoped,导致捕获过期的 Scoped 实例

  • 解决 :改用 IServiceScopeFactory 在需要时创建新作用域

cs 复制代码
public class SingletonService
{
    private readonly IServiceScopeFactory _scopeFactory;
​
    public SingletonService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }
​
    public void DoWork()
    {
        using var scope = _scopeFactory.CreateScope();
        var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
        // 安全使用 scoped 服务
    }
}
相关推荐
周末也要写八哥1 小时前
浅谈:C++中cpp 14 ~ cpp 17
开发语言·c++·算法
不会C语言的男孩1 小时前
C++ Primer 第13章:拷贝控制
开发语言·c++
z落落1 小时前
C# 静态成员 vs 非静态成员(调用规则+内存特点)+只读和常量 const常量 / readonly / static readonly 三者终极区别
java·开发语言·c#
zhangfeng11331 小时前
超算中心 高性能计算 slurm的linux版本 centos7,如何安装docker,如何安装torch2.4
linux·运维·服务器·开发语言·人工智能·机器学习·docker
java1234_小锋1 小时前
LangChain4j 开发Java Agent智能体- 整合SpringBoot4
java·开发语言·langchain4j
我不是懒洋洋2 小时前
从零实现一个Redis客户端:RESP协议与网络编程
开发语言·c++
小小码农Come on2 小时前
Qt::WA_StyledBackground属性的作用
开发语言·qt
许彰午2 小时前
04_Java数组操作全解
java·开发语言·python
码不停蹄的玄黓2 小时前
Java 线程池 execute() 和 submit() 对比
java·开发语言