首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >具有两个依赖服务的循环依赖关系

具有两个依赖服务的循环依赖关系
EN

Stack Overflow用户
提问于 2021-05-27 15:09:44
回答 1查看 264关注 0票数 2

我是C#和依赖注入方面的新手。目前,我正在做一个新的项目,并希望做一个技术进步。

在这个项目中,我有三种导致循环依赖的情况。

我已经读了很多关于这个问题的文章,并且找到了像Lazy<T>IServiceProvider这样的解决方案,但是我想学习一个解决这个问题的干净的解决方案,并且希望遵循最常见的建议来重构代码。

在这个例子中我们有四个服务:

AccountService ->登录、注销等等

HttpService ->做API的事

LogService ->做一些日志记录

用于EF日志表/包装器的LogRepository -> CRUD

AccountService使用HttpService通过API进行身份验证。稍后,我希望使用HttpService通过API获取更多数据。HttpService现在需要AccountService来获取对请求进行身份验证的令牌。这会导致循环依赖错误。

AccountService

代码语言:javascript
复制
public interface IAccountService
{
    Identity Identity { get; }
    Task Login(Credentials Credentials);
    Task Logout();
}

public class AccountService : IAccountService
{
    public Identity Identity { get; private set; }
    
    private readonly IHttpService _httpService;
    private readonly ILogService _logService;
    
    public AccountService(
        IHttpService HttpService, ILogService LogService)
    {
        _httpService = HttpService;
        _logService = LogService;
    }

    public async Task Login(Credentials Credentials)
    {
        Identity = await _httpService.Post<Identity>(
            "api/rest/v1/user/authenticate", Credentials);
    }
}

HttpService

代码语言:javascript
复制
public interface IHttpService
{
    Task<T> Get<T>(string uri);
    Task Post(string uri, object value);
    Task<T> Post<T>(string uri, object value);
}

public class HttpService : IHttpService
{
    private readonly HttpClient _httpClient;
    private readonly IAccountService _accountService;
    private readonly ILogService _logService; 

    public HttpService(
        HttpClient HttpClient,
        IAccountService AccountService,
        ILogService ILogService)
    {
        _httpClient = HttpClient;
        _accountService = AccountService;
        _logService = LogService;
    }

    private async Task AddAuthentication(HttpRequestMessage Request)
    {
        Request.Headers.Authorization = new AuthenticationHeaderValue(
            "bearer", _accountService.Identity.SystemToken);
    }
}

解决或适当重新设计这个问题的最佳实践是什么?

我有更多的循环依赖项,例如在LogRepository中使用LogRepository或在HttpService中使用LogService (因为HttpService向服务器发送日志入口)。

非常感谢你的帮助!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-05-28 14:52:08

虽然对象图是循环的(AccountService -> HttpService -> AccountService),但调用图不是。这一呼吁可能如下所示:

代码语言:javascript
复制
AccountService.Login
    -> HttpService.Post
        -> HttpService.AddAuthentication
            -> AccountService.Identity

具有非循环调用图的循环对象图经常发生在违反单一负责任原则的组件上.功能(方法)类越多,其对象图循环的可能性就越大。将类拆分成更小、更集中的部分,不仅解决了循环依赖问题,而且还经常改进应用程序的设计。

我认为您的情况实际上与我在第6.3节 of DIPP&P中讨论的示例非常相似。这一节专门讨论了修复循环依赖关系。

长话短说,我认为您最好的选择是将AccountService分成(至少)两个服务:

  • 一个负责登录和注销的服务
  • 负责获取用户身份的第二个服务。

这两个服务都有自己的接口,与IAccountService相比,这些新接口的宽度现在更小了。这提高了你加入界面偏析原理的机会。

下面是一个这样的例子:

让我们从新的接口定义开始:

代码语言:javascript
复制
// Contains Login and Logout methods of old IAccountService
public interface IAuthenticationService
{
    Task Login(Credentials Credentials);
    Task Logout();
}

// Contains Identity property of old IAccountService
public interface IIdentityProvider
{
    // For simplicity I added a setter to the interface, because that keeps
    // the example simple, but it is possible to keep Identity read-only if
    // required.
    Identity Identity { get; set; }
}

// This interface is kept unchanged.
public interface IHttpService
{
    Task<T> Get<T>(string uri);
    Task Post(string uri, object value);
    Task<T> Post<T>(string uri, object value);
}

下面让我们看一下实现,从IAuthenticationService实现开始:

代码语言:javascript
复制
// Old AccountService, now depending on IIdentityProvider
public class AuthenticationService : IAuthenticationService
{
    private readonly IHttpService _httpService;
    private readonly ILogService _logService;
    private readonly IIdentityProvider _identityProvider;
    
    public AccountService(
        IHttpService HttpService,
        ILogService LogService,
        IIdentityProvider IdentityProvider)
    {
        _httpService = HttpService;
        _logService = LogService;
        _identityProvider = IdentityProvider;
    }

    public async Task Login(Credentials Credentials)
    {
        _identityProvider.Identity = await _httpService.Post<Identity>(
            "api/rest/v1/user/authenticate", Credentials);
    }
}

这个“新”AuthenticationService包含AccountService的部分代码,旧的AccountService逻辑的其余部分隐藏在新的IIdentityProvider抽象之后,该抽象被注入到AuthenticationService中。这种重构非常类似于外观服务重构 (关于Facade服务重构的详细讨论,请参阅DIPP&P的第6.1节 )。

IdentityProvider实现了新的IIdentityProvider接口,并包含来自AccountService的旧逻辑。

代码语言:javascript
复制
public class IdentityProvider : IIdentityProvider
{
    public Identity Identity { get; set; }
}

最后,HttpService现在依赖于IIdentityProvider而不是IAccountService

代码语言:javascript
复制
// Now depends on IIdentityProvider instead of IAccountService
public class HttpService : IHttpService
{
    private readonly HttpClient _httpClient;
    private readonly IIdentityProvider _identityProvider;
    private readonly ILogService _logService; 

    public HttpService(
        HttpClient HttpClient,
        IIdentityProvider IdentityProvider,
        ILogService ILogService)
    {
        _httpClient = HttpClient;
        _identityProvider = IdentityProvider;
        _logService = LogService;
    }

    private async Task AddAuthentication(HttpRequestMessage Request)
    {
        // Now uses the new IIdentityProvider dependency instead
        // of the old IAccountService, which caused the cycle.
        Request.Headers.Authorization = new AuthenticationHeaderValue(
            "bearer", _identityProvider.Identity.SystemToken);
    }
}

使用这一新设计,对象图不再是循环的,可以按照以下方式构造:

代码语言:javascript
复制
var identity = new IdentityProvider();
var logger = new LogService();

new AccountService(
    new HttpService(
        new HttpClient(...),
        identity,
        logger),
    logger,
    identity);
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67725031

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档