具有两个依赖服务的循环依赖 [英] Circular Dependency with two depending Services
问题描述
我对 C# 和依赖注入非常陌生.目前我正在从事一个新项目,并希望在技术上向前迈进.
I'm very new in C# and Dependency Injection. Currently I'm working on a new project and want to do a technology step forward.
在这个项目中,我遇到了三种导致循环依赖的情况.
In this Project, I've three situation causing circular dependency.
我已经阅读了很多关于此的内容并找到了诸如 Lazy<T>
和 IServiceProvider
之类的解决方案,但我想为这个问题学习一个干净的解决方案,并且想要遵循最常见的建议来重构代码.
I've read a lot about this and found solutions like Lazy<T>
and and IServiceProvider
, but I want to learn a clean solution for this problem and want to follow the most common suggestion to refactor the code.
在这个例子中我们有四个服务:
We have four services in this example:
AccountService
->登录、注销等
HttpService
->做 API 的事情
HttpService
-> Do the API-Stuff
LogService
->做一些日志记录
LogRepository
->用于 EF 的记录表/包装器的 CRUD
LogRepository
-> CRUD for the logging table / wrapper for EF
AccountService
使用 HttpService
通过 API 进行身份验证.后来,我想使用 HttpService
通过 API 获取更多数据.HttpService
现在需要 AccountService
来获取用于验证请求的令牌.这会导致循环依赖错误.
The AccountService
authenticate via API using HttpService
. Later, I want use the HttpService
to get more data via API. HttpService
now need the AccountService
to get the Token for authenticate the request. This is causing a circular dependency error.
账户服务
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
HttpService
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);
}
}
解决或正确重新设计此问题的最佳实践是什么?
How is the best practice to solve or proper redesign this?
我有更多的循环依赖,例如在 LogRepository
中使用 LogService
或在 HttpService
中使用 LogService
(因为 HttpService
发送到服务器的日志条目).
I've more Circular Dependency, e.g. use the LogService
in LogRepository
or using LogService
in HttpService
(because the HttpService
sends Log-Entrys to the Server).
非常感谢您的帮助!
推荐答案
虽然你的对象图是循环的 (AccountService
-> HttpService
-> AccountService
) 你的调用图不是.电话可能是这样的:
Although your object graph is cyclic (AccountService
-> HttpService
-> AccountService
) your call graph is not. The call likely is something as follows:
AccountService.Login
-> HttpService.Post
-> HttpService.AddAuthentication
-> AccountService.Identity
带有非循环调用图的
循环对象图经常发生在违反单一责任原则.类获得的功能(方法)越多,它们的对象图变得循环的机会就越大.将类拆分为更小、更集中的部分,不仅可以解决循环依赖问题,而且通常还可以改进应用程序的设计.
Cyclic object graphs with non-cyclic call graphs often happen on components that violate the Single Responsibly Principle. The more functionality (methods) classes get, the bigger the chance their object graphs become cyclic. Splitting classes up in smaller, more focused pieces, not only fixes the cyclic-dependency problem, but often also improves the design of the application.
我认为您的情况实际上与我在 DIPP&P 的第 6.3 节.该部分专门讨论修复循环依赖.
I think your case is actually quite similar to the example that I discuss in section 6.3 of DIPP&P. That section specifically talks about fixing cyclic dependencies.
长话短说,我认为最好的办法是将 AccountService
拆分为(至少)两个服务:
Long story short, I think your best bet is to split AccountService
in (at least) two services:
- 一项负责登录和注销的服务
- 负责获取用户身份的第二项服务.
这两个服务都有自己的接口,而且与 IAccountService
相比,这些新接口现在的范围更小.这提高了您遵守接口隔离原则的机会.
Both services get their own interface and those new interfaces are now less wide compared to IAccountService
. This improves your chances of adhering to the Interface Segregation Principle.
这是一个示例:
让我们从新的接口定义开始:
Let's start with the new interface definitions:
// 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
实现开始:
Let's look at the implementations next, starting with the IAuthenticationService
implementation:
// 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 Service 重构(详细讨论关于 Facade Service 重构,请参阅 第 6.1 节 的 DIPP&P).
This "new" AuthenticationService
contains part of the code of the AccountService
and the rest of the old AccountService
logic is hidden behind the new IIdentityProvider
abstraction, which is injected into AuthenticationService
. This refactoring is very similar to the Facade Service refactoring (for an elaborate discussion on the Facade Service refactoring, see section 6.1 of DIPP&P).
IdentityProvider
实现新的 IIdentityProvider
接口并包含来自 AccountService
的旧逻辑:
IdentityProvider
implements the new IIdentityProvider
interface and contains the old logic from AccountService
:
public class IdentityProvider : IIdentityProvider
{
public Identity Identity { get; set; }
}
最后,HttpService
现在依赖于 IIdentityProvider
而不是 IAccountService
:
And finally, HttpService
that now depends on IIdentityProvider
instead of IAccountService
:
// 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);
}
}
使用这种新设计,对象图不再是循环的,可以构造如下:
Using this new design, the object graph is no longer cyclic and can be constructed as follows:
var identity = new IdentityProvider();
var logger = new LogService();
new AccountService(
new HttpService(
new HttpClient(...),
identity,
logger),
logger,
identity);
这篇关于具有两个依赖服务的循环依赖的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!