Подтвердить что ты не робот

Зависимость Композиция корня и декоратора

Я получаю StackoverflowException в моей реализации шаблона декоратора при использовании инъекции зависимостей. Я думаю, что это потому, что я "пропускаю" что-то из своего понимания 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>();

        }

Итак, теперь я не уверен в правильном способе создания декоратора с инъекцией зависимости. Я основал свой декоратор на Mark Seemann, отвечая здесь. В его примере он создает несколько объектов, которые передаются в класс. Вот как мой "работает" * работает фрагмент. Однако, я думаю, что я пропустил фундаментальный шаг.

Почему вручную создавать новые объекты, подобные этому? Разве это не отрицает того, что контейнер для меня разрешает? Или я должен вместо этого contain.Resolve() (локатор сервисов) в рамках этого одного метода, чтобы все вложенные зависимости все еще были?

Я немного знаком с концепцией "составной корень", в которой вы должны подключать эти зависимости в одном и только одном месте, которое затем каскадируется до нижних уровней приложения. Итак, Unity.Mvc сгенерированный RegisterTypes() корень композиции приложения ASP.NET MVC? Если это так, правильно ли здесь быть непосредственно новыми объектами?

У меня создалось впечатление, что, как правило, с Unity вам нужно создать собственный корневой состав, однако Unity.Mvc является исключением из этого в том, что он создает свой собственный корневой состав, поскольку он, похоже, способен вводить зависимости в контроллеры которые имеют интерфейс, такой как ICustomerService в конструкторе, без того, что я пишу код, чтобы сделать это.

Вопрос. Я считаю, что мне не хватает ключевой информации, которая приводит меня к StackoverflowExceptions из-за круговых зависимостей. Как я правильно реализую свой класс декоратора, все еще следуя за зависимостями впрыска/инверсии принципов и соглашений управления?

Второй вопрос: А если я решил, что я только хотел применить декоратор журнала в определенных обстоятельствах? Поэтому, если у меня было MyController1, что я хотел иметь зависимость CustomerServiceLoggingDecorator, но MyController2 нужен только обычный CustomerService, как мне создать две отдельные регистрации? Потому что, если я это сделаю:

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();

Затем будет перезаписано значение, означающее, что оба контроллера будут либо снабжены инжектором декоратора, либо обычной инъекцией. Как мне разрешить оба?

Изменить: это не дублирующий вопрос, потому что у меня возникают проблемы с круговыми зависимостями и недостаточным пониманием правильного подхода DI для этого. Мой вопрос относится ко всей концепции, а не только шаблон декоратора, как связанный вопрос.

4b9b3361

Ответ 1

Преамбула

Всякий раз, когда у вас возникают проблемы с контейнером DI (Unity или иным способом), спросите себя: использует контейнер DI, который стоит усилий?

В большинстве случаев ответ должен быть не. Вместо этого используйте Pure DI. Все ваши ответы тривиальны, чтобы ответить Pure DI.

Unity

Если вы должны использовать Unity, возможно, следующее поможет. Я не использовал Unity с 2011 года, поэтому с тех пор ситуация изменилась, но в разделе 14.3.3 рассмотрена проблема в в моей книге, что-то вроде этого может сделать трюк:

container.RegisterType<ICustomerService, CustomerService>("custSvc");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionContructor(
        new ResolvedParameter<ICustomerService>("custSvc")));

Кроме того, вы также можете это сделать:

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(
        new ResolvedParameter<CustomerService>()));

Этот вариант проще поддерживать, поскольку он не полагается на именованные службы, но имеет (потенциальный) недостаток, который невозможно разрешить CustomerService через интерфейс ICustomerService. Вы, вероятно, не должны этого делать, так что это не должно быть проблемой, поэтому, вероятно, это лучшая альтернатива.

Ответ 2

Вопрос: Я считаю, что мне не хватает ключевого фрагмента информации, что приводит меня к выводам StackoverflowException из-за круговых зависимостей. Как я правильно реализую свой класс декоратора, все еще следуя за зависимостями впрыска/инверсии принципов и соглашений управления?

Как уже указывалось, лучший способ сделать это - со следующей конструкцией.

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 с именем, указанным выше в регистрации. Таким образом, для этого конструктора вместо CustomerServiceLoggingDecorator type будет введен тип CustomerService. MyController1 по-прежнему будет использовать невынужденную регистрацию по умолчанию для ICustomerService, которая является типом CustomerServiceLoggingDecorator.

public MyController2([Dependency("plainService")]ICustomerService service) 

public MyController1(ICustomerService service) 

Есть также способы сделать это, когда вы вручную разрешаете тип в самом контейнере, см. "Разрешение объектов с помощью переопределений" . Проблема здесь в том, что вам нужен доступ к самому контейнеру, чтобы сделать это, что не рекомендуется. В качестве альтернативы вы можете создать обертку вокруг контейнера, которую затем вводите в контроллер (или другой тип), а затем извлекаете тип таким образом с помощью переопределений. Опять же, это становится немного грязным, и я бы избегал его, если это было возможно.

Ответ 3

На основе второго ответа Mark я бы хотел зарегистрировать 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>()