【问题标题】:Is it possible to deal with circular references in .net core 2 dependency injection?是否可以处理 .net core 2 依赖注入中的循环引用?
【发布时间】:2019-07-15 15:37:56
【问题描述】:

N 层设计,我有 UserRepository 负责数据库的工作,UserManager 负责业务逻辑,还有一个控制器。

我为订单发送OrderManagerOrderRepository

在我的UserManager中,有DoUserStuffThenAddAnOrder()之类的方法

这个方法会做一些与用户相关的事情,然后在我的OrderManager 上调用AddOrder()

所以我将OrderManager 依赖注入到我的UserManager 中,以便它可以调用与订单相关的方法。

但是我在OrderManager 遇到了同样的问题,我想DoOrderStuffThenChangeAUser(),所以我将UserManager 输入OrderManager

然后我的 DI 因为循环引用而中断;创建一个 OrderManager 需要将一个 UserManager 注入其中,注入一个 usermanager 需要一个 ordermanager,等等等等。

有解决办法吗?也许是一种告诉它在 DI 设置中将自己作为参数传入的方法?

作为一种解决方法,我创建了一个OrderManagerFactory 和一个UserManagerFactory,并在每个上创建了两个重载的构造函数,例如,创建新实例的控制器将 DI 一个 OrderManagerFactory,它在构造函数中将调用 .GetOrderManager(this)重载的构造函数,反之亦然。

但是有两个构造函数不是很好,如果我创建更多的管理器,那么它可能会创建一个注入和构造函数的地狱。

有没有一个巧妙的解决方案?其他问题表明,如果您遇到此问题,您的软件架构不正确,但 SRP 会要求每个经理做自己的事情并允许他们互相调用以使业务逻辑独立,这似乎是合理的。

这是一个例子:

public class UserManager {

    private IOrderManager _orderManager;

    public UserManager(IOrderManager orderManager)
    {
        _orderManager = orderManager;
    }

    public void DeleteUser(int userId) {

         if (_orderManager.GetOrdersForUser(userId).Count != 0) {
             throw new Exception("Cannot delete user while orders exist");
         }

         // Do a bunch of stuff connected with deleting a user

         // Send them a goodbye e-mail
         EmailUser(123, "Goodbye");

    }

    public void EmailUser(int userId, string message)
    {
        var userMail = _repository.GetUser(userId).EmailAddress;
        _emailSomething.Send(userMail, message);
    }
}

public class OrderManager {

    private IUserManager _userManager;

    public OrderManager(IUserManager userManager)
    {
        _userManager = userManager;
    }

    public IEnumerable<OrderDto> GetOrdersForUser(int userId)
    {
        return Map(_repo.GetOrdersForUserId(userId));
    }

    public void CancelOrder(int orderId)
    {
        var userId = _repo.GetOrder(orderId).UserId;

        // Cancel order
        var user = _userManager.GetUserById(userId);

        _userManager.EmailUser(userId, "Hello " + user.Name + ", your order has been cancelled");
    }
}

这是不可能的,因为 IOrderManager 和 IUserManager 都不能在没有循环引用错误的情况下注入。

【问题讨论】:

  • 如果不使用中间抽象(例如工厂、Lazy 等),就不可能将这些对象 new 起来
  • 你认为基础设计有问题吗?一种选择是代替UserManager&gt;OrderManager&gt;OrderRepository 来简单地注入存储库,然后执行UserManager&gt;OrderRepository。但是这样做的风险是你会削减或不得不复制一些业务逻辑。
  • 设计似乎比它需要的更复杂,而且 IMO 有点重复比管理循环依赖更好。
  • 循环依赖通常是由违反单一责任原则引起的。将您的课程分成更小、更集中的课程可能会很好地解决您的问题。
  • 我不知道你如何进一步拆分它,如果UserManager 有一个SendEmailToUser() 方法,你可能想在订单完成时在你的OrderManager 中调用它,同样如果用户被删除,您可能需要在您的OrderManager 上调用CancelAllOrders()。我看不出你可以如何简化它,这样他们就不需要在没有一点点麻烦的情况下互相交谈。

标签: dependency-injection asp.net-core-2.0 circular-dependency


【解决方案1】:

您的课程听起来好像做得太多了。解决方案是使用更可靠的方法重构您的代码。 DI 工作得非常好,但你遇到问题的事实表明你的方法存在问题,这是你需要重新思考的指标——这就是整个模式的目的之一

【讨论】:

  • 问题是通过将 User 的东西放在 User 类中来遵循 SRP 是 SOLID,而 Order 的东西放在 Order 类中,但是当它们都需要互相调用时,这就是整个事情的中断向下。和 MVC 本身一样,有时一个控制器会重定向到另一个控制器,有时那个控制器会重定向到原来的控制器,但是控制器之间没有依赖注入,我们可以直接路由到一个 URL,框架会自动为我们整理一下。所以我正在考虑有一个单例 ManagerManager 里面的经理。
  • 尽量避免单例。它是另一种“ServiceLocator”类型的反模式。到目前为止,最简单的解决方案是将常用方法从 Orders 和 Users 移出到不同的类中,然后只将您需要的方法注入到 Orders 和 Users 类中。冲洗并重复,直到你的循环依赖消失。这是解决问题的“正确”方法
  • 你最终会把整个班级都拉出来,你可以从你的订单管理器中调用几乎任何用户函数,甚至像GetUserById 这样你就可以获取他们的名字或其他任何东西。反之亦然,GetOrdersForUser() 显示计数或其他内容。
  • 您能否用一些示例代码更新您的问题,使其更易于可视化?这可能会让我更容易表达我的观点
猜你喜欢
  • 2018-03-03
  • 2020-12-20
  • 1970-01-01
  • 2017-03-24
  • 2016-05-22
  • 1970-01-01
  • 2021-07-15
  • 2022-01-27
  • 2021-10-23
相关资源
最近更新 更多