背景:
如果你想成为一名c# 开发高级工程师(鼠鼠还不是),那么你会听到很多专业的术语,对于非科班的鼠鼠来说,理解起来真的非常吃力,于是鄙鼠站在非科班同学的角度下去理解什么是依赖注入,希望对各位大佬有所帮助
什么是依赖注入
在 C# 中,依赖注入(Dependency Injection,简称 DI) 是一种设计模式,用于减少代码之间的耦合度,核心思想是:"依赖的对象不由自身创建,而是由外部传入"。
依赖注入的好处
- 解耦:类只依赖抽象(接口),不依赖具体实现,便于替换实现。
- 便于测试:可以轻松替换为模拟对象(如单元测试中的
MockLogger
)。 - 集中管理:依赖的创建和生命周期由外部容器统一管理,减少重复代码。
- 可维护性:修改依赖实现时,无需修改使用它的类
这样解释起来有点抽象,上点具体使用案例,再在后面总结,你要会依赖注入,那就必须得知道什么是接口什么是接口实现
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 Server和MySQL的数据库操作,
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
接口的实例时,应该创建哪个具体的实现类(比如SqlServerUserRepository
或MySqlUserRepository
)”。
// 注册仓储:告诉容器,当需要IUserRepository时,创建SqlServerUserRepository
services.AddScoped<IUserRepository, SqlServerUserRepository>();
// 如果换成MySQL,就注册这个:
// services.AddScoped<IUserRepository, MySqlUserRepository>();
这行代码的意义是:
通俗的讲
- 建立映射关系:容器记住 “
IUserRepository
接口 →SqlServerUserRepository
实现类” 的对应关系。 - 自动创建实例:当
UserService
需要IUserRepository
时(通过构造函数),容器会自动创建SqlServerUserRepository
的实例,并注入进去。 - 灵活切换实现:如果后期要换成 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
类中的方法(如AddUser
、GetUserById
等),而不是直接调用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
这个具体的业务服务(或其接口,如果定义了的话),而不是依赖数据访问层的接口。这样可以进一步隔离业务逻辑和外部调用,即使未来业务层重构,外部调用方式也能保持稳定。