#c# #dependency-injection #circular-dependency #anemic-domain-model
#c# #внедрение зависимостей #циклическая зависимость #anemic-domain-model
Вопрос:
Я работаю над проектом со сложным бизнесом. Рассмотрим два класса: AccountService и SchoolService
Я использую Unity и распознаватель зависимостей веб-API для реализации внедрения зависимостей в конструктор.
Школьная служба использует службу учетных записей в некоторых методах, также служба учетных записей использует школьную службу. Все это требуется для бизнеса проекта. Это приведет к циклической зависимости, и невозможно переместить методы из одного класса в другой.
Можете ли вы, пожалуйста, предоставить какую-либо идею о том, как это решить?
Вот пример:
public class SchoolBLC : ISchoolBLC
{
public School GetSchool(int schoolId)
{
...
}
public bool RenewRegistration(int accountId)
{
bool result = true;
IAccountBLC accountBLC = new AccountBLC();
// check some properties related to the account to decide if the account can be renewed
// ex : the account should not be 5 years old
// check the account created date and do renewal
return resu<
}
}
public class AccountBLC : IAccountBLC
{
public void ResetAccount(int accountId)
{
ISchoolBLC schoolBLC = new SchoolBLC();
School accountSchool = schoolBLC
// get the school related to the account to send a notification
// and tell the school that the user has reset his account
// reset account and call the school notification service
}
public Account GetAccount(int accountId)
{
...
}
}
Два класса ссылаются друг на друга, это ситуация для 70% BLC в проекте.
Комментарии:
1. Можете ли вы привести пример такой циклической зависимости в вашем проекте.
2. Звучит как плохой дизайн… Я бы выделил общие вещи для 3-й службы. Это решит циклическую зависимость. DI-движки обычно генерируют исключение для циклической ссылки.
3. @SledgeHammer Ближе к делу. Как бы вы решили проблему даже без DI? DI не волшебный, если вы не можете сделать это без него, вы не можете сделать это с ним.
4. Я хотел бы добавить к ответу Кувалды. Ваша проблема в том, что у вас есть два класса, которые тесно связаны. Если они связаны на 70%, не означает ли это, что они являются одной и той же единицей кода? Рассмотрим основной принцип ООП, инкапсуляцию; вы должны предоставлять как можно меньше функциональности. Когда у вас есть два класса с методами, которые вызываются только друг другом, ВЫ НЕ ВЫПОЛНЯЕТЕ ИНКАПСУЛЯЦИЮ. Это означает, что ваш код не является ООП.
5. @Aron. Нет, это не так. Я предполагаю, что если интерфейс A amp; B имеет много общего кода / функциональности, он должен быть либо в базовом классе, либо разбит на интерфейс C. Затем A amp; B используют интерфейс C вместо B amp; A, и вы прерываете циклическую ссылку.
Ответ №1:
Если вам абсолютно необходимо сделать это таким образом, у вас может быть интерфейс, который выполняет вашу логику IoC и разрешает ее в реализации, которая переносит разрешение Unity, например
public interface ITypeResolver
{
T Resolve<T>();
}
Затем вы можете передать этот интерфейс обеим службам в конструкторе и использовать его для отложенного разрешения другой службы перед ее использованием вне конструктора.
Таким образом, при инициализации обеих служб они не будут иметь прямой зависимости от другой службы, только от ITypeResolver
Ответ №2:
Я сделаю так, как предложил @KMoussa, но с некоторыми изменениями:
В проекте используется анемичная модель, поэтому я буду использовать шаблон контекста для отложенной загрузки и создания любой службы, а контекст будет передан в качестве параметра конструктору службы.
public class SDPContext : ISDPContext
{
private ITypeResolver _typeResolver;
public Account CurrentUser { get; set; }
public IAccountService AccountService
{
get
{
// lazy load the account service
}
}
public ISchoolService SchoolService
{
get
{
// lazy load the schoolservice
}
}
public SDPContext(ITypeResolver typeResolver)
{
this._typeResolver = typeResolver;
}
}
public class ServiceBase
{
public ISDPContext CurrentContext { get; set; }
public ServiceBase(ISDPContext context)
{
this.CurrentContext = context;
}
}
public class AccountService : ServiceBase, IAccountService
{
public AccountService(ISDPContext context) : base(context)
{
}
public bool ResetAccount(int accountId)
{
// use base.Context.SchoolService to access the school business
}
}
public class SchoolService : ServiceBase, ISchoolService
{
public SchoolService(ISDPContext context) : base(context)
{
//this._accountService = accountService;
}
public void RenewRegistration(int accountId)
{
// use the base.Context.Account service to access the account service
}
}