依赖注入架构设计——服务类循环引用
本文关键字:服务 循环 引用 注入 依赖 | 更新日期: 2023-09-27 17:49:30
我有以下服务类:
public class JobService {
private UserService us;
public JobService (UserService us) {
this.us = us;
}
public void addJob(Job job) {
// needs to make a call to user service to update some user info
// similar dependency to the deleteUser method
}
}
public class UserService {
private JobService js;
public UserService(JobService js) {
this.js = js;
}
public void deleteUser(User u) {
using (TransactionScope scope = new TransactionScope()) {
List<IJob> jobs = jobService.findAllByUser(u.Id);
foreach (IJob job in jobs) {
js.deleteJob(job);
}
userDao.delete(user);
scope.Complete();
}
}
}
每个服务类都是由IoC容器实例化的,没有功能问题,但对我来说,这种方法有潜在的设计缺陷,我想知道是否有一种更有意义的替代方法。
正如有人已经指出的那样,问题不在于DI容器的限制,而在于您的设计。
我明白你有一个单独的UserService
和一个JobService
的原因,它们包含了对彼此的引用。这是因为UserService
和JobService
都包含一些需要其他服务作为引用的逻辑(添加作业需要添加用户等)。但是,我认为您不应该从另一个服务引用一个服务。相反,您应该在服务背后有另一个抽象层,服务将使用该抽象层来实现公共逻辑。因此,服务将包含不能(不应该)被重用的逻辑,而助手将包含共享逻辑。
public class UserHelper{
//add all your common methods here
}
public class JobService {
private UserHelper us;
public JobService (UserHelper us) {
this.us = us;
}
public void addJob(Job job) {
// calls helper class
}
}
public class UserService {
public UserService(UserHelper js) {
this.js = js;
}
public void deleteUser(User u) {
// calls helper class
}
}
通过这种方式,你将不会有任何循环引用的问题,并且你将有一个地方包含需要被不同服务重用的逻辑。
另外,我更喜欢那些彼此完全隔离的服务。
您遇到的问题实际上与DI容器的限制无关,但这是一个普遍问题。即使没有任何容器,也不可能创建这些类型:
var job = new JobService([what goes here???]);
var user = new UserService(job);
因此,一般的答案是将其中一个依赖项提升为属性。这将打破依赖循环:
var job = new JobService();
var user = new UserService(job);
// Use property injection
job.User = user;
防止使用超过严格需要的属性。这些依赖循环应该非常罕见,这使得将类型连接在一起或验证DI配置的正确性变得更加困难。构造函数注入使得这更容易。
您可以通过使用事件来解耦服务。当执行某个操作时,将引发一个事件,而不是调用另一个服务的依赖方法。然后,集成商可以通过事件将服务连接起来。一个服务甚至不知道另一个服务的存在。
public class JobService
{
public event Action<User, Job> JobAdded;
public void AddJob(User user, Job job)
{
//TODO: Add job.
// Fire event
if (JobAdded != null) JobAdded(user, job);
}
internal void DeleteJobs(int userID)
{
//TODO: Delete jobs
}
}
public class UserService
{
public event Action<User> UserDeleted;
public void DeleteUser(User u)
{
//TODO: Delete User.
// Fire event
if (UserDeleted != null) UserDeleted(u);
}
public void UpdateUser(User user, Job job)
{
//TODO: Update user
}
}
集成器连接服务
public static class Services
{
public static JobService JobService { get; private set; }
public static UserService UserService { get; private set; }
static Services( )
{
JobService = new JobService();
UserService = new UserService();
JobService.JobAdded += JobService_JobAdded;
UserService.UserDeleted += UserService_UserDeleted;
}
private static void UserService_UserDeleted(User user)
{
JobService.DeleteJobs(user.ID);
}
private static void JobService_JobAdded(User user, Job job)
{
UserService.UpdateUser(user, job);
}
}
(注意:我简化了一些事件引发。它不是线程安全的。但是您可以假设这些事件是预先订阅的,以后不会更改。
这在autoface中不起作用。请参阅文档中的循环依赖项部分。
构造函数/构造函数依赖两种带有循环的类型不支持构造函数依赖项。你会得到一个异常当您尝试解析以这种方式注册的类型时。
你可以使用关系类型(Func<>
, Lazy<>
)来打破这个循环。
你的代码有点太通用了,不能想出一个合适的解决方案,但是你应该考虑改变依赖关系的方向,不管你使用什么IoC容器。
public class JobService {
private UserService us;
public JobService (UserService us) {
this.us = us;
}
public void addJob(Job job) {
// needs to make a call to user service to update some user info
}
}
public class UserService {
private JobService js;
public UserService(Func<JobService> jsFactory) {
this.js = jsFactory(this);
}
public void deleteUser(User u) {
// needs to call the job service to delete all the user's jobs
}
}
或者,在您的示例中,您可以移动deleteUser
并创建一个方法,删除作业服务上的所有作业,而不是使用id引用用户。这通过使用id来打破依赖关系。
deleteUser
。