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

Использование фреймворков Dependency Injection для классов со многими зависимостями

Я рассматривал различные схемы внедрения зависимостей для .NET, поскольку я чувствую, что проект, над которым я работаю, принесет большую пользу. Хотя я думаю, что я хорошо разбираюсь в возможностях этих фреймворков, я все еще немного неясен о том, как лучше внедрить их в большую систему. Большинство демо (по понятным причинам) имеют довольно простые классы, которые имеют одну или две зависимости.

У меня есть три вопроса...

Сначала, как вы справляетесь с этими общими, но неинтересными зависимостями, например. ILog, IApplicationSettings, IPermissions, IAudit. Кажется, излишним для каждого класса иметь эти параметры в своем конструкторе. Было бы лучше использовать статический экземпляр контейнера DI, чтобы получить их, когда они понадобятся?

MyClass(ILog log, IAudit audit, IPermissions permissions, IApplicationSettings settings)
// ... versus ...
ILog log = DIContainer.Get<ILog>();

Второй, как вы подходите к зависимостям, которые могут быть использованы, но могут быть дорогими для создания. Пример. Класс может иметь зависимость от интерфейса ICDBurner, но не хочет, чтобы конкретная реализация была создана, если не была использована функция записи компакт-дисков. Переходите ли вы в интерфейсы к фабрикам (например, ICDBurnerFactory) в конструкторе, или вы снова отправляетесь с каким-то статическим способом напрямую обращаться к контейнеру DI и спрашиваете его в той точке, в которой это необходимо?

Третий, предположим, что у вас большое приложение Windows Forms, в котором компонент GUI верхнего уровня (например, MainForm) является родителем потенциально сотен суб-панелей или модальных форм, каждый из которых может имеют несколько зависимостей. Означает ли это, что MainForm должен быть настроен так, чтобы иметь в качестве зависимостей надмножество всех зависимостей своих детей? И если бы вы это сделали, разве это не привело бы к созданию огромного самонадувающегося монстра, который строит каждый отдельный класс, который ему когда-либо понадобится, когда вы создадите MainForm, тратя время и память в процессе?

4b9b3361

Ответ 1

Сначала:. При необходимости добавьте простые зависимости к вашему конструктору. Нет необходимости добавлять каждый тип к каждому конструктору, просто добавьте те, которые вам нужны. Нужна еще одна, просто расширьте конструктор. Производительность не должна быть большой вещью, так как большинство из этих типов, вероятно, будут синглонами, которые уже созданы после первого вызова. Не используйте статический контейнер DI для создания других объектов. Вместо этого добавьте контейнер DI в себя, чтобы он мог решить себя как зависимость. Итак, что-то вроде этого (предполагая единство на данный момент)

IUnityContainer container = new UnityContainer();
container.RegisterInstance<IUnityContainer>(container);

Таким образом, вы можете просто добавить зависимость от IUnityContainer и использовать его для создания дорогостоящих или редко необходимых объектов. Главное преимущество заключается в том, что при модульном тестировании намного проще, поскольку нет статических зависимостей.

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

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

Ответ 2

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

То, что я хотел бы рассмотреть в вашем примере, - это разбить класс в нескольких более последовательных классах с целенаправленными проблемами, и, следовательно, количество их зависимостей будет падать.

Закон Николая SRP и DI

"Любой класс, имеющий более 3 должны быть подвергнуты сомнению Нарушение SRP"

(Чтобы избежать длительного ответа, я подробно разместил свои ответы на IoC и SRP в блоге)

Ответ 3

Во-первых:

Вы можете вводить эти объекты, когда это необходимо, в качестве элементов, а не в конструкторе. Таким образом, вам не нужно вносить изменения в конструктор в соответствии с вашими изменениями в использовании, и вам также не нужно использовать статику.

Второе:

Перейдите в какой-либо конструктор или factory.

Третье:

Любой класс должен иметь только те зависимости, которые он сам требует. Подклассам следует вводить свои собственные зависимости.

Ответ 4

У меня есть аналогичный случай, связанный с "дорогостоящим созданием и может быть использован", где в моей собственной реализации IoC я добавляю автоматическую поддержку служб factory.

В основном, вместо этого:

public SomeService(ICDBurner burner)
{
}

вы бы сделали это:

public SomeService(IServiceFactory<ICDBurner> burnerFactory)
{
}

ICDBurner burner = burnerFactory.Create();

Это имеет два преимущества:

  • За кулисами контейнер обслуживания, который разрешил вашу службу, также используется для разрешения записи, если и когда она запрашивается.
  • Это облегчает проблемы, которые я видел ранее, в этом случае, когда типичным способом было бы ввести сам контейнер обслуживания в качестве параметра для вашей службы, в основном говоря: "Для этой услуги требуются другие услуги, но я не легко сказать вам, какие из них"

Объект factory довольно прост в изготовлении и решает множество проблем.

Здесь мой класс factory:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LVK.IoC.Interfaces;
using System.Diagnostics;

namespace LVK.IoC
{
    /// <summary>
    /// This class is used to implement <see cref="IServiceFactory{T}"/> for all
    /// services automatically.
    /// </summary>
    [DebuggerDisplay("AutoServiceFactory (Type={typeof(T)}, Policy={Policy})")]
    internal class AutoServiceFactory<T> : ServiceBase, IServiceFactory<T>
    {
        #region Private Fields

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly String _Policy;

        #endregion

        #region Construction & Destruction

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="serviceContainer">The service container involved.</param>
        /// <param name="policy">The policy to use when resolving the service.</param>
        /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception>
        public AutoServiceFactory(IServiceContainer serviceContainer, String policy)
            : base(serviceContainer)
        {
            _Policy = policy;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="serviceContainer">The service container involved.</param>
        /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception>
        public AutoServiceFactory(IServiceContainer serviceContainer)
            : this(serviceContainer, null)
        {
            // Do nothing here
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the policy that will be used when the service is resolved.
        /// </summary>
        public String Policy
        {
            get
            {
                return _Policy;
            }
        }

        #endregion

        #region IServiceFactory<T> Members

        /// <summary>
        /// Constructs a new service of the correct type and returns it.
        /// </summary>
        /// <returns>The created service.</returns>
        public IService<T> Create()
        {
            return MyServiceContainer.Resolve<T>(_Policy);
        }

        #endregion
    }
}

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

Это позволяет мне сделать это:

var builder = new ServiceContainerBuilder();
builder.Register<ISomeService>()
    .From.ConcreteType<SomeService>();

using (var container = builder.Build())
{
    using (var factory = container.Resolve<IServiceFactory<ISomeService>>())
    {
        using (var service = factory.Instance.Create())
        {
            service.Instance.DoSomethingAwesomeHere();
        }
    }
}

Конечно, более типичное использование будет с вашим объектом CD Burner. В приведенном выше коде я, конечно, решил бы эту услугу, но это иллюстрация того, что происходит.

Итак, вместо вашей службы записи cd:

var builder = new ServiceContainerBuilder();
builder.Register<ICDBurner>()
    .From.ConcreteType<CDBurner>();
builder.Register<ISomeService>()
    .From.ConcreteType<SomeService>(); // constructor used in the top of answer

using (var container = builder.Build())
{
    using (var service = container.Resolve<ISomeService>())
    {
        service.Instance.DoSomethingHere();
    }
}

внутри службы, теперь у вас может быть служба, служба factory, которая знает, как разрешить вашу службу записи компакт-дисков по запросу. Это полезно по следующим причинам:

  • Возможно, вы захотите одновременно разрешить несколько сервисов (одновременно записывайте два диска?)
  • Возможно, вам это не понадобится, и это может быть дорогостоящим для создания, поэтому вы разрешите его при необходимости
  • Возможно, вам придется разрешать, удалять, разрешать, удалять, несколько раз, вместо того, чтобы надеяться/пытаться очистить существующий экземпляр службы.
  • Вы также отмечаете в своем конструкторе, какие службы вам нужны, и какие из них вам нужны.

Здесь два в то же время:

using (var service1 = container.Resolve<ISomeService>())
using (var service2 = container.Resolve<ISomeService>())
{
    service1.Instance.DoSomethingHere();
    service2.Instance.DoSomethingHere();
}

Здесь два после друг друга, не повторяя одну и ту же услугу:

using (var service = container.Resolve<ISomeService>())
{
    service.Instance.DoSomethingHere();
}
using (var service = container.Resolve<ISomeService>())
{
    service.Instance.DoSomethingElseHere();
}

Ответ 5

Во-первых:

Вы можете подойти к нему, создав контейнер для хранения ваших "неинтересных" зависимостей (ILog, ICache, IApplicationSettings и т.д.) и используйте инъекцию конструктора, чтобы вставить это, а затем внутреннее для конструктора, увлажнить поля службы из контейнера.Разрешить()? Я не уверен, что мне это понравится, но, возможно, это возможность.

В качестве альтернативы вы можете использовать новый общий интерфейс IServiceLocator (http://blogs.msdn.com/gblock/archive/2008/10/02/iservicelocator-a-step-toward-ioc-container-service-locator-detente.aspx) вместо того, чтобы вводить зависимости?

Второе:

Вы можете использовать инъекцию setter для дополнительных/зависимых по запросу? Я думаю, что я поеду за инъекционными фабриками и новыми оттуда по требованию.

Ответ 6

Чтобы частично ответить на мой первый вопрос, я только что нашел сообщение