如何解决服务层内部的循环依赖

Mar*_*ann 7 c# asp.net dependency-injection asp.net-core

我知道其他人已经遇到了同样的问题,但我找不到任何令人满意的解决方案,所以我在这里询问其他想法。

我的业务逻辑包含在这样的服务层中:

public class RoomService : IRoomService
{
    private readonly IRoomRepository _roomRepository;
    private readonly ICourseService _courseService;

    public RoomService(IRoomRepository roomRepository, ICourseService courseService)
    {
        _roomRepository = roomRepository ?? throw new ArgumentNullException(nameof(roomRepository));
        _courseService = courseService ?? throw new ArgumentNullException(nameof(courseService));
    }

    public Task DeleteRoomAsync(string id)
    {
        // Check if there are any courses for this room (requires ICourseService)
        // Delete room
    }
}

public class CourseService : ICourseService
{
    private readonly ICourseRepository _courseRepository;
    private readonly IRoomService _roomService;

    public CourseService(ICourseRepository courseRepository, IRoomService roomService)
    {
        _courseRepository = courseRepository ?? throw new ArgumentNullException(nameof(courseRepository));
        _roomService = roomService ?? throw new ArgumentNullException(nameof(roomService));
    }

    public Task GetAllCoursesInBuilding(string buildingId)
    {
        // Query all rooms in building (requires IRoomService)
        // Return all courses for these rooms
    }
}
Run Code Online (Sandbox Code Playgroud)

这只是一个例子。在这种情况下,可能有解决方法可以避免服务相互依赖,但我过去遇到过多种其他情况,没有任何干净的解决方法。

正如您所看到的,这两个服务相互依赖,并且依赖注入将由于循环依赖而失败。

现在我可以想象两种方法来解决这个问题:

解决方案1

我可以解决需要它们的服务方法内部的服务依赖关系,而不是将服务依赖关系注入到服务构造函数中:

public class RoomService : IRoomService
{
    private readonly IRoomRepository _roomRepository;
    private readonly IServiceProvider _serviceProvider;

    public RoomService(IRoomRepository roomRepository, IServiceProvider serviceProvider)
    {
        _roomRepository = roomRepository ?? throw new ArgumentNullException(nameof(roomRepository));
        _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
    }

    public Task DeleteRoomAsync(string id)
    {
        ICourseService courseService = _serviceProvider.GetRequiredService<ICourseService>();

        // Check if there are any courses for this room (requires ICourseService)
        // Delete room
    }
}
Run Code Online (Sandbox Code Playgroud)

问题:这使得单元测试变得更加困难,因为我需要注入一个IServiceProvider能够将 my 解析ICourseService到类构造函数中的模拟。此外,在编写单元测试时,每个服务方法需要哪些服务也不是很清楚,因为这完全依赖于实现。

解决方案2

服务方法可能要求ICourseService从控制器作为方法参数传入:

public Task DeleteRoomAsync(ICourseService courseService, string id)
{
    // Check if there are any courses for this room (requires ICourseService)
    // Delete room
}
Run Code Online (Sandbox Code Playgroud)

问题:现在我的控制器需要了解服务方法的实现细节:DeleteRoomAsync需要一个ICourseService对象来完成它的工作。 我认为这不是很干净,因为未来的要求DeleteRoomAsync可能会改变,但方法签名不应该改变。

您能想到任何替代的、更清洁的解决方案吗?

Joh*_* Wu 3

如果您的框架支持它,您可以提供注入的依赖项作为Lazy<T>延迟解析并允许您具有循环依赖项。

这些服务类可能如下所示:

class FooService : IFooService
{
    protected Lazy<IBarService> _bar;

    public FooService(Lazy<IBarService> bar)
    {
        _bar = bar;
    }

    public void DoSomething(bool callOtherService)
    {
        Console.WriteLine("Hello world. I am Foo.");
        if (callOtherService)
        {
            _bar.Value.DoSomethingElse(false);
        }
    }

}

class BarService : IBarService
{
    protected Lazy<IFooService> _foo;

    public BarService(Lazy<IFooService> foo)
    {
        _foo = foo;
    }
    public void DoSomethingElse(bool callOtherService)
    {
        Console.WriteLine("Hello world. I am Bar.");
        if (callOtherService)
        {
            _foo.Value.DoSomething(false);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注册它们的代码不需要修改(至少使用 Autofac 不需要修改):

public static IContainer CompositionRoot()
{
    var builder = new ContainerBuilder();
    builder.RegisterType<FooService>().As<IFooService>().SingleInstance();
    builder.RegisterType<BarService>().As<IBarService>().SingleInstance();
    builder.RegisterType<Application>().SingleInstance();
    return builder.Build();
}
Run Code Online (Sandbox Code Playgroud)

请参阅DotNetFiddle上的工作示例。

如果您的框架不支持这样的延迟注入,您可能可以使用工厂(或任何其他延迟解析的模式)执行完全相同的操作。

另请参阅这个答案,它帮助我提出了这个解决方案。


归档时间:

查看次数:

8810 次

最近记录:

6 年,4 月 前