c#之依赖注入学习

背景:
如果你想成为一名c# 开发高级工程师(鼠鼠还不是),那么你会听到很多专业的术语,对于非科班的鼠鼠来说,理解起来真的非常吃力,于是鄙鼠站在非科班同学的角度下去理解什么是依赖注入,希望对各位大佬有所帮助


什么是依赖注入

在 C# 中,依赖注入(Dependency Injection,简称 DI) 是一种设计模式,用于减少代码之间的耦合度,核心思想是:"依赖的对象不由自身创建,而是由外部传入"

依赖注入的好处

  1. 解耦:类只依赖抽象(接口),不依赖具体实现,便于替换实现。
  2. 便于测试:可以轻松替换为模拟对象(如单元测试中的MockLogger)。
  3. 集中管理:依赖的创建和生命周期由外部容器统一管理,减少重复代码。
  4. 可维护性:修改依赖实现时,无需修改使用它的类

这样解释起来有点抽象,上点具体使用案例,再在后面总结,你要会依赖注入,那就必须得知道什么是接口什么是接口实现

1、什么是接口和接口实现

接口(Interface) 是一种抽象类型,它定义了一组方法、属性或事件的 "契约",但不提供具体实现。接口的作用是规定 "必须做什么",而具体 "怎么做" 则由实现接口的类来决定。

1. 接口的定义

接口使用interface关键字定义,命名通常以I开头(约定):

我们通过数据库对人员的增删改查来做模拟,定义了一个用户数据模型嘞类,里面有三个字段,分别是Id,Name和Email,定义了一个接口IUserRepository,里面定义了四个增删改查的方法

// 用户数据模型
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

// 数据库操作接口(定义标准)
public interface IUserRepository
{
    User GetById(int id);       // 根据ID查询用户
    void Add(User user);        // 添加用户
    void Update(User user);     // 更新用户
    void Delete(int id);        // 删除用户
}
2. 接口的实现

类通过:符号实现接口,并必须实现接口中的所有成员即“怎么做”

定义实现接口,我们创建两个实现类,这里数据库框架我选择的是sqlsugar,你也可以采用EFcore,分别模拟SQL ServerMySQL的数据库操作,

using SqlSugar;
using System;

public class MySqlUserRepository : IUserRepository
{
    // SqlSugar客户端(通过构造函数注入)
    private readonly ISqlSugarClient _db;

    // 构造函数注入SqlSugar客户端
    public MySqlUserRepository(ISqlSugarClient db)
    {
        _db = db;
    }

    public User GetById(int id)
    {
        try
        {
            // 使用SqlSugar查询单条数据
            return _db.Queryable<User>().In(id).Single();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"MySQL查询失败: {ex.Message}");
            throw;
        }
    }

    public void Add(User user)
    {
        try
        {
            // 使用SqlSugar插入数据
            _db.Insertable(user).ExecuteCommand();
            Console.WriteLine($"MySQL: 用户 {user.Name} 添加成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"MySQL添加失败: {ex.Message}");
            throw;
        }
    }

    public void Update(User user)
    {
        try
        {
            // 使用SqlSugar更新数据
            _db.Updateable(user).ExecuteCommand();
            Console.WriteLine($"MySQL: 用户 {user.Id} - {user.Name} 更新成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"MySQL更新失败: {ex.Message}");
            throw;
        }
    }

    public void Delete(int id)
    {
        try
        {
            // 使用SqlSugar删除数据
            _db.Deleteable<User>().In(id).ExecuteCommand();
            Console.WriteLine($"MySQL: ID为{id}的用户删除成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"MySQL删除失败: {ex.Message}");
            throw;
        }
    }
}

public class SqlServerUserRepository : IUserRepository
{
    // SqlSugar客户端(通过构造函数注入,由DI容器管理)
    private readonly ISqlSugarClient _db;

    // 构造函数注入SqlSugar客户端
    public SqlServerUserRepository(ISqlSugarClient db)
    {
        _db = db;
    }

    public User GetById(int id)
    {
        try
        {
            // 使用SqlSugar查询单条数据
            return _db.Queryable<User>().In(id).Single();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"SQL Server查询失败: {ex.Message}");
            throw; // 抛出异常让上层处理
        }
    }

    public void Add(User user)
    {
        try
        {
            // 使用SqlSugar插入数据
            _db.Insertable(user).ExecuteCommand();
            Console.WriteLine($"SQL Server: 用户 {user.Name} 添加成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"SQL Server添加失败: {ex.Message}");
            throw;
        }
    }

    public void Update(User user)
    {
        try
        {
            // 使用SqlSugar更新数据
            _db.Updateable(user).ExecuteCommand();
            Console.WriteLine($"SQL Server: 用户 {user.Id} - {user.Name} 更新成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"SQL Server更新失败: {ex.Message}");
            throw;
        }
    }

    public void Delete(int id)
    {
        try
        {
            // 使用SqlSugar删除数据
            _db.Deleteable<User>().In(id).ExecuteCommand();
            Console.WriteLine($"SQL Server: ID为{id}的用户删除成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"SQL Server删除失败: {ex.Message}");
            throw;
        }
    }
}
    

2. 业务服务的实现

        业务服务类(比如UserService)的核心作用很简单:把 “数据操作” 和 “业务规则” 分开,只做和业务相关的事情(比如验证、判断逻辑),而实际存数据、取数据的工作交给IUserRepository去做。

// 业务服务类:只处理业务逻辑,不碰具体数据库操作
public class UserService
{
    // 依赖数据操作接口(具体是SQL Server还是MySQL,这里不关心)
    private readonly IUserRepository _userRepository;

    // 构造函数注入:外部传入数据操作对象
    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    // 1. 新增用户(带简单业务验证)
    public void AddUser(User user)
    {
        // 业务规则:邮箱不能为空
        if (string.IsNullOrEmpty(user.Email))
        {
            throw new ArgumentException("用户邮箱不能为空!");
        }

        // 业务规则:邮箱必须包含@符号
        if (!user.Email.Contains("@"))
        {
            throw new ArgumentException("邮箱格式不正确!");
        }

        // 真正存数据的工作,交给数据层(IUserRepository)
        _userRepository.Add(user);
    }

    // 2. 根据ID获取用户(无复杂业务,直接调用数据层)
    public User GetUserById(int id)
    {
        // 业务规则:ID不能小于0
        if (id <= 0)
        {
            throw new ArgumentException("ID必须是正数!");
        }

        // 调用数据层获取数据
        return _userRepository.GetById(id);
    }

    // 3. 更新用户(带业务验证)
    public void UpdateUser(User user)
    {
        // 业务规则:必须存在ID才能更新
        if (user.Id <= 0)
        {
            throw new ArgumentException("用户ID不能为空!");
        }

        // 调用数据层更新数据
        _userRepository.Update(user);
    }

    // 4. 删除用户(带业务权限验证)
    public void DeleteUser(int id, bool isAdmin)
    {
        // 业务规则:只有管理员能删除
        if (!isAdmin)
        {
            throw new UnauthorizedAccessException("没有权限删除用户!");
        }

        // 调用数据层删除数据
        _userRepository.Delete(id);
    }
}
为什么需要业务服务类?

简单说,就是 “谁的活谁干”:

  • IUserRepository(数据层):只负责 “怎么存、怎么取” 数据(和数据库打交道)。
  • UserService(业务层):只负责 “该不该存、该不该取”(处理业务规则、验证、权限等)。

比如新增用户时:

  • 数据层(SqlServerUserRepository)只做 “把用户信息写入数据库” 这个动作。
  • 业务层(UserService)先判断 “邮箱格式对不对”,对了才让数据层去存。

3. DI配置

现在我们可以通过依赖注入容器(DI 容器) 来管理依赖的创建和注入了,解释一下什么是注册仓储
“注册仓储” 是依赖注入(DI)中的一个关键步骤,简单说就是告诉 DI 容器:“当需要IUserRepository接口的实例时,应该创建哪个具体的实现类(比如SqlServerUserRepositoryMySqlUserRepository)”

// 注册仓储:告诉容器,当需要IUserRepository时,创建SqlServerUserRepository
services.AddScoped<IUserRepository, SqlServerUserRepository>();

// 如果换成MySQL,就注册这个:
// services.AddScoped<IUserRepository, MySqlUserRepository>();

这行代码的意义是:

通俗的讲

  1. 建立映射关系:容器记住 “IUserRepository接口 → SqlServerUserRepository实现类” 的对应关系。
  2. 自动创建实例:当UserService需要IUserRepository时(通过构造函数),容器会自动创建SqlServerUserRepository的实例,并注入进去。
  3. 灵活切换实现:如果后期要换成 MySQL,只需要改这一行代码,其他依赖IUserRepository的类(如UserService)完全不用动。
using Microsoft.Extensions.DependencyInjection;
using SqlSugar;

public static class DiConfig
{
    // 扩展方法:注册数据库和服务
    public static void AddAppServices(this IServiceCollection services)
    {
        // 1. 注册SqlSugar客户端(SQL Server)
        services.AddScoped<ISqlSugarClient>(provider =>
        {
            var db = new SqlSugarClient(new ConnectionConfig
            {
                ConnectionString = "Server=.;Database=TestDb;Uid=sa;Pwd=YourPassword;", // 替换为实际连接字符串
                DbType = DbType.SqlServer,
                IsAutoCloseConnection = true // 自动关闭连接
            });
            return db;
        });

        // 若使用MySQL,注释上面的SQL Server配置,启用下面的配置
        /*
        services.AddScoped<ISqlSugarClient>(provider =>
        {
            var db = new SqlSugarClient(new ConnectionConfig
            {
                ConnectionString = "Server=localhost;Database=TestDb;Uid=root;Pwd=YourPassword;Port=3306;",
                DbType = DbType.MySql,
                IsAutoCloseConnection = true
            });
            return db;
        });
        */

        // 2. 注册仓储(根据数据库类型选择)
        services.AddScoped<IUserRepository, SqlServerUserRepository>();
        // services.AddScoped<IUserRepository, MySqlUserRepository>(); // MySQL使用这个

        // 3. 注册业务服务
        services.AddScoped<UserService>();
    }
}
 

总结

步骤 1:建立实体层(实体类)

作用:定义数据模型(即建立和数据库一一对应的表单),映射业务实体(如用户、订单等),作为数据传递的载体。
 

步骤 2:编写接口和接口实现(数据访问层)

作用

  • 接口(如IUserRepository)定义数据操作的标准(“做什么”),隔离具体实现。
  • 接口实现(如MySqlUserRepository 或SqlServerUserRepository)提供具体的数据库操作(“怎么做”),依赖 ORM(如 SqlSugar或者EFCore)。

步骤 3:编写业务服务类(业务逻辑层)

作用:依赖接口(而非具体实现),处理业务规则(验证、权限等),不直接操作数据库。即告诉店员你要喝什么奶茶

步骤 4:注册服务(DI 配置)

作用:告诉 DI 容器 “接口对应哪个实现”,以及对象的生命周期,让容器自动管理依赖。

public static class DiConfig
{
    public static void AddAppServices(this IServiceCollection services)
    {
        // 1. 注册ORM客户端(如SqlSugar,连接数据库)
        services.AddScoped<ISqlSugarClient>(_ => new SqlSugarClient(new ConnectionConfig
        {
            ConnectionString = "你的数据库连接字符串",
            DbType = DbType.SqlServer,
            IsAutoCloseConnection = true
        }));

        // 2. 注册仓储(接口→实现的映射)
        services.AddScoped<IUserRepository, SqlServerUserRepository>();

        // 3. 注册业务服务(容器会自动注入依赖的IUserRepository)
        services.AddScoped<UserService>();
    }
}

注意:

在实际使用时,应该应该直接调用UserService类中的方法(如AddUserGetUserById等),而不是直接调用IUserRepository接口或其实现类

  • 数据层(SqlServerUserRepository/MySqlUserRepository)只做 “把用户信息写入数据库” 这个动作。
  • 业务层(UserService)先判断 “邮箱格式对不对”,对了才让数据层去存。

比如下面的使用方法

// 从DI容器获取业务服务(依赖已自动注入)
var userService = serviceProvider.GetRequiredService<UserService>();

// 调用业务服务的方法(而非直接调用IUserRepository)
try
{
    // 添加用户(自动触发业务验证)
    userService.AddUser(new User { Name = "张三", Email = "zhangsan@test.com" });

    // 查询用户(自动验证ID合法性)
    var user = userService.GetUserById(1);
    Console.WriteLine($"查询到用户:{user.Name}");
}
catch (Exception ex)
{
    Console.WriteLine($"操作失败:{ex.Message}");
}
为什么这么做?
  • 业务逻辑封装在UserService
    UserService不仅包含数据操作,还封装了业务规则(如邮箱验证、权限检查等)。如果绕过UserService直接调用IUserRepository,会导致业务规则被忽略,出现逻辑漏洞。

    例如:直接调用_userRepository.Add(user)可能会插入一个邮箱为空的用户,但通过UserService.AddUser(user)会先验证邮箱格式,确保数据合法性。

  • IUserRepository是 “内部依赖”
    IUserRepository及其实现类(如SqlServerUserRepository)属于 “数据访问层”,是UserService的内部依赖,而非给外部直接使用的。

    就像汽车的发动机(数据层)是给变速箱(业务层)用的,用户只需要操作方向盘(调用业务服务),不需要直接操控发动机。

  • 符合 “依赖抽象” 原则
    外部代码(如控制器、控制台逻辑)应该依赖UserService这个具体的业务服务(或其接口,如果定义了的话),而不是依赖数据访问层的接口。

    这样可以进一步隔离业务逻辑和外部调用,即使未来业务层重构,外部调用方式也能保持稳定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值