依赖注入体系结构设计 - 服务类循环引用

时间:2012-12-26 15:33:45

标签: c# dependency-injection inversion-of-control circular-dependency

我有以下服务类:

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容器实例化的,并且没有功能问题,但这对我来说感觉这种方法存在潜在的设计缺陷,我想知道是否存在一种替代方法更有意义。

4 个答案:

答案 0 :(得分:6)

正如有人已经指出的那样,问题不在于DI容器的限制,而在于您的设计。

我看到你有一个单独的UserService和一个JobService的原因,它们包含对彼此的引用。这是因为UserServiceJobService都包含一些需要其他服务作为参考的逻辑(添加作业需要添加用户等)。但是,我认为你不应该引用另一个服务。相反,您应该在服务后面使用另一层抽象,服务将用于通用逻辑。因此,服务将包含不能(不应该)重用的逻辑,并且帮助程序将包含共享逻辑。

例如:

    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
      }
    }   

通过这种方式,您将不会遇到循环引用的任何问题,并且您将拥有一个包含需要由不同服务重用的逻辑的位置。

另外,我更喜欢提供彼此完全隔离的服务。

答案 1 :(得分:2)

您遇到的问题实际上与您的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配置的正确性要困难得多。构造函数注入使这更容易。

答案 2 :(得分:1)

这不适用于Autofac。请参阅文档的circular dependencies部分。

  

构造函数/构造函数依赖项带循环的两种类型   不支持构造函数依赖项。你会得到一个例外   当您尝试解决以这种方式注册的类型时。

您可以使用relationship typesFunc<>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

答案 3 :(得分:1)

您可以使用事件来解耦服务。在执行操作时,不是调用另一个服务的依赖方法,而是引发事件。然后,集成商可以通过事件连接服务。服务甚至不知道其他服务的存在。

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);
    }
}

(注意:我简化了一些事件。这不是线程安全的。但你可以假设事件是事先订阅的,以后不会更改。)