Зависимость Инъекционная композиция, корень и шаблон декоратора
Я собираюсь StackruException
в моей реализации шаблона декоратора при использовании внедрения зависимостей. Я думаю, это потому, что я "что-то упускаю" из моего понимания DI/IoC.
Например, у меня в настоящее время есть CustomerService
а также CustomerServiceLoggingDecorator
, Оба класса реализуют ICustomerService
и все, что делает класс декоратора, это использует ICustomerService
но добавляет несколько простых журналов NLog, чтобы я мог использовать журналирование, не влияя на код в CustomerService
в то же время не нарушая принцип единой ответственности.
Однако проблема здесь в том, что, потому что CustomerServiceLoggingDecorator
инвентарь ICustomerService
и это также нуждается в реализации ICustomerService
введенный в него, чтобы работать, Unity будет продолжать пытаться разрешить его обратно себе, что вызывает бесконечный цикл, пока он не переполнит стек.
Это мои услуги:
public interface ICustomerService
{
IEnumerable<Customer> GetAllCustomers();
}
public class CustomerService : ICustomerService
{
private readonly IGenericRepository<Customer> _customerRepository;
public CustomerService(IGenericRepository<Customer> customerRepository)
{
if (customerRepository == null)
{
throw new ArgumentNullException(nameof(customerRepository));
}
_customerRepository = customerRepository;
}
public IEnumerable<Customer> GetAllCustomers()
{
return _customerRepository.SelectAll();
}
}
public class CustomerServiceLoggingDecorator : ICustomerService
{
private readonly ICustomerService _customerService;
private readonly ILogger _log = LogManager.GetCurrentClassLogger();
public CustomerServiceLoggingDecorator(ICustomerService customerService)
{
_customerService = customerService;
}
public IEnumerable<Customer> GetAllCustomers()
{
var stopwatch = Stopwatch.StartNew();
var result = _customerService.GetAllCustomers();
stopwatch.Stop();
_log.Trace("Querying for all customers took: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
return result;
}
}
В настоящее время у меня есть настройки регистрации, как это (Этот метод заглушки был создан Unity.Mvc
):
public static void RegisterTypes(IUnityContainer container)
{
// NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
// container.LoadConfiguration();
// TODO: Register your types here
// container.RegisterType<IProductRepository, ProductRepository>();
// Register the database context
container.RegisterType<DbContext, CustomerDbContext>();
// Register the repositories
container.RegisterType<IGenericRepository<Customer>, GenericRepository<Customer>>();
// Register the services
// Register logging decorators
// This way "works"*
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(
new CustomerService(
new GenericRepository<Customer>(
new CustomerDbContext()))));
// This way seems more natural for DI but overflows the stack
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
}
Так что теперь я не уверен в "правильном" способе создания декоратора с внедрением зависимостей. Я основал свой декоратор на ответе Марка Симанна здесь. В своем примере он обновляет несколько объектов, которые передаются в класс. Вот как мой это "работает"* работает фрагмент. Тем не менее, я думаю, что я пропустил фундаментальный шаг.
Зачем вручную создавать новые объекты, как это? Разве это не сводит на нет смысл того, что контейнер решает за меня? Или я должен вместо этого сделать contain.Resolve()
(сервисный локатор) в этом одном методе, чтобы получить все зависимости еще?
Я немного знаком с понятием "составной корень", в котором вы должны соединить эти зависимости в одном-единственном месте, которое затем перемещается вниз к нижним уровням приложения. Так это Unity.Mvc
генерироваться RegisterTypes()
корень композиции приложения ASP.NET MVC? Если это так, то правильно ли здесь прямо обновлять объекты?
У меня сложилось впечатление, что, как правило, в Unity вам нужно создавать корень композиции самостоятельно, однако Unity.Mvc
Исключением является то, что он создает собственный корень композиции, потому что кажется, что он может вводить зависимости в контроллеры, имеющие интерфейс, такой как ICustomerService
в конструкторе без меня писать код, чтобы заставить это сделать это.
Вопрос: я считаю, что мне не хватает ключевой информации, которая ведет меня к StackruExceptions
из-за круговых зависимостей. Как правильно реализовать мой класс декоратора, при этом следуя принципам внедрения и инверсии зависимостей?
Второй вопрос: а что, если я решу, что хочу применять декоратор регистрации только при определенных обстоятельствах? Так что если бы я MyController1
что я хотел бы иметь CustomerServiceLoggingDecorator
зависимость, но MyController2
нужен только нормальный CustomerService
Как мне создать две отдельные регистрации? Потому что если я сделаю:
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();
Затем один из них будет перезаписан, что означает, что оба контроллера будут либо вставлять декоратор, либо обычное обслуживание. Как я могу учесть оба?
Изменить: Это не повторяющийся вопрос, потому что у меня есть проблемы с циклическими зависимостями и отсутствие понимания правильного подхода DI для этого. Мой вопрос относится ко всей концепции, а не только к шаблону декоратора, как связанный вопрос.
3 ответа
преамбула
Всякий раз, когда у вас возникают проблемы с DI-контейнером (Unity или иным), спросите себя: стоит ли использовать DI-контейнер?
В большинстве случаев ответ должен быть отрицательным. Вместо этого используйте Pure DI. Все ваши ответы тривиальны, чтобы ответить с Pure DI.
Единство
Если вы должны использовать Unity, возможно, вам помогут следующие. Я не пользуюсь Unity с 2011 года, поэтому с тех пор все могло измениться, но, глядя на проблему в разделе 14.3.3 в моей книге, что-то вроде этого может помочь:
container.RegisterType<ICustomerService, CustomerService>("custSvc");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(
new ResolvedParameter<ICustomerService>("custSvc")));
Кроме того, вы также можете сделать это:
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(
new ResolvedParameter<CustomerService>()));
Эту альтернативу проще поддерживать, поскольку она не зависит от именованных сервисов, но имеет (потенциальный) недостаток, который вы не можете устранить CustomerService
сквозь ICustomerService
интерфейс. Вы, вероятно, не должны делать это в любом случае, так что это не должно быть проблемой, так что это, вероятно, лучшая альтернатива.
Вопрос: Я считаю, что мне не хватает ключевой информации, которая приводит меня к StackruExceptions из-за циклических зависимостей. Как правильно реализовать мой класс декоратора, при этом следуя принципам внедрения и инверсии зависимостей?
Как уже указывалось, лучший способ сделать это с помощью следующей конструкции.
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
Это позволяет указать, как параметры разрешаются по типу. Вы также можете сделать это по имени, но по типу это более чистая реализация и позволяет лучше проверять во время компиляции, так как изменение или неправильный тип в строке не будут обнаружены. Обратите внимание, что единственная минутная разница между этой частью кода и кодом, предложенным Марком Симэнном, - это исправление в написании InjectionConstructor
, Я не буду более подробно останавливаться на этой части, поскольку больше нечего добавить, что Марк Симанн еще не объяснил.
Второй вопрос: а что, если я решу, что хочу применять декоратор регистрации только при определенных обстоятельствах? Поэтому, если у меня был MyController1, который я хотел бы иметь зависимостью CustomerServiceLoggingDecorator, но MyController2 нужен только обычный CustomerService, как мне создать две отдельные регистрации?
Вы можете сделать это указанным выше способом, используя нотацию Fluent ИЛИ используя именованную зависимость с переопределением зависимости.
беглый
Это регистрирует контроллер в контейнере и определяет перегрузку для этого типа в конструкторе. Я предпочитаю этот подход второму, но он зависит только от того, где вы хотите указать тип.
container.RegisterType<MyController2>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
Именованная зависимость
Вы делаете это точно так же, вы регистрируете их обоих так.
container.RegisterType<ICustomerService, CustomerService>("plainService");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
Разница здесь в том, что вы используете именованную зависимость вместо других типов, которые могут быть разрешены с использованием того же интерфейса. Это связано с тем, что интерфейс должен быть разрешен ровно к одному конкретному типу каждый раз, когда Unity выполняет разрешение, чтобы вы не могли иметь несколько безымянных зарегистрированных типов, зарегистрированных на одном и том же интерфейсе. Теперь вы можете указать переопределение в вашем конструкторе контроллера, используя атрибут. Мой пример для контроллера с именем MyController2
и я добавил Dependency
Атрибут с именем, также указанным выше при регистрации. Так что для этого конструктора CustomerService
тип будет введен вместо значения по умолчанию CustomerServiceLoggingDecorator
тип. MyController1
будет по-прежнему использовать безымянную регистрацию по умолчанию для ICustomerService
который является типом CustomerServiceLoggingDecorator
,
public MyController2([Dependency("plainService")]ICustomerService service)
public MyController1(ICustomerService service)
Есть также способы сделать это, когда вы вручную разрешаете тип на самом контейнере, см. Разрешение объектов с помощью переопределений. Проблема в том, что для этого вам нужен доступ к самому контейнеру, что не рекомендуется. В качестве альтернативы вы можете создать оболочку вокруг контейнера, которую затем внедряете в контроллер (или другой тип), а затем извлекаете тип таким образом с переопределениями. Снова, это становится немного грязным, и я избежал бы этого, если возможно.
Опираясь на второй ответ Марка, я бы посмотрел на регистрацию CustomerService
с InjectionFactory
и только зарегистрируйте его с типом сервиса без его интерфейса, как:
containter.RegisterType<CustomerService>(new InjectionFactory(
container => new CustomerService(containter.Resolve<IGenericRepository<Customer>>())));
Это позволит вам, как и в ответе Марка, зарегистрировать объект регистрации следующим образом:
containter.RegisterType<ICutomerService, CutomerServiceLoggingDecorator>(new InjectionConstructor(
new ResolvedParameter<CustomerService>()));
Это в основном та же техника, которую я использую всякий раз, когда мне требуется, чтобы что-то загружалось лениво, так как я не хочу, чтобы мои объекты зависели Lazy<IService>
и, оборачивая их в прокси, позволяет мне только вводить IService
но разрешите это лениво через прокси.
Это также позволит вам выбирать, куда вводится объект регистрации или обычный объект, вместо того, чтобы требовать magic
строки, просто решая CustomerService
для вашего объекта вместо ICustomerService
,
Для регистрации CustomerService
:
container.Resolve<ICustomerService>()
Или для не регистрации CustomerService
:
container.Resolve<CustomerService>()