Winforms IOC Container - корень композиции

Недавно я немного поболтал с контейнерами IOC (в моем случае LightInject).

Я читал, что вам нужно использовать контейнер только ОДИН РАЗ, при запуске, и нигде больше. Это то, что я пытаюсь понять. Если я могу ссылаться на контейнер только в методе начальной загрузки / запуска, как можно решить, что мне нужно, в другом месте проекта или во время выполнения, если класс зависит от ввода пользователя.

Так что в моем традиционном приложении для Windows Forms, при загрузке формы скажу, я бы загрузил Lightinject согласно приведенному ниже коду. Это всего лишь произвольный пример, это скорее предпосылка, в которой я должен разобраться.

Я мог бы что-то здесь упустить полностью или просто не получить это. Но как мне разрешить зависимости, если я не могу использовать / не должен ссылаться или использовать Container.GetInstance/Resolve/{Выбрать синтаксис IOC здесь}, и только в корне композиции.

Например, в моей форме есть две кнопки и текстовое поле. Первая кнопка дает мне ILoader (код ниже), а вторая кнопка загружает средство просмотра файлов (ILoader, код ниже), имя файла которого совпадает с тем, которое вводится в текстовое поле winform.

Без контейнера IOC я бы сделал следующее (давайте просто предположим, что он помещен в событие click)

Кнопка 1 Нажмите Событие:

ISplitText MyStringFunc =  new WhateverImplementsIt();

Кнопка 2 (получает средство чтения файлов на основе ввода текстового поля)

ILoader MyLoader = new FileReaderImplementation(TextBox1.Text);

Используя LightInject, я, безусловно, вынужден сделать следующее:

Button1 Нажмите:

ISplitText Splitter = Container.GetInstance<ISplitText>();

Кнопка 2 Нажмите

var LoaderFunc = Container.GetInstance<Func<string, ILoader>>();
ILoader l2 = LoaderFunc(TextBox1.Text);            

Я не прав? В большом проекте у меня будет Container.GetInstance, добавленный повсюду, в основном файле формы и в любом другом месте, конечно, так как я могу ссылаться на контейнер ТОЛЬКО в 1 месте, в форме начальной загрузки, я пропускаю магию часть головоломки?

Во всех примерах приложений, которые я видел, все это делается в одном простом консольном приложении в функции Main. Все эти приложения имеют формат:

Container = new Container();
Container.Register<IFoo,Foo>();
Container.Register<IBar,Bar();

var Resolved = Container.GetInstance<IFoo>();

Ну, я все это понимаю, и это очень просто. Как только вы начинаете добавлять немного сложности к самому приложению, я теряюсь в том, как получить экземпляры, не делая сам Контейнер общедоступным, статичным или доступным каким-либо образом, в форме или форме, а затем вызывая Container.GetInstance. в миллион мест (что, видимо, большой нет, нет). ПОЖАЛУЙСТА ПОМОГИ! Ура,

чудь

PS - Меня не волнует само "абстрагирование контейнера". поэтому предпочел бы сосредоточиться только на расширении моего понимания вышесказанного.

public class BootStrapIOC
{
    public ServiceContainer Container;
    public BootStrapIOC(ServiceContainer container)
    {
        Container = container;
    }

    public void Start()
    {
        Container.Register<ISplitText, StringUtil>();
        Container.Register<string, ILoader>((factory, value) => new FileViewByFileName(value));


    }
}



//HUH? How can i NOT use the container??, in this case in the button_click
ILoader Loader = Container.GetInstance<Func<string, ILoader>>();
ILoader l2 = Loader(TextBox1.Text);            
ISplitText Splitter = Container.GetInstance<ISplitText>();

РЕДАКТИРОВАТЬ #1

Итак, перечитав комментарии и посмотрев еще несколько примеров на веб-сайте, я думаю, что, наконец, смогу это понять. Проблема была (я думаю) в том, что я не думал о "более высоком уровне". Я пытался разрешить свои зависимости в приложении winforms, ПОСЛЕ того, как форма уже была создана, и в самой форме. Когда на самом деле, уже слишком поздно. Я не рассматривал "саму форму" как просто еще один объект, который нуждался в том, чтобы в него вставляли зависимости.

Итак, я загружаюсь сейчас в моем Program.cs:

static class Program
{
    private static ServiceContainer Container;

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Container = new ServiceContainer();
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        BootStrapIOC Strap = new BootStrapIOC(Container);

        Strap.Start();
        //This magic line resolves EVERYTHING for me required by the Form
        var form = Container.GetInstance<Form1>();
        Application.Run(form);
        //Application.Run(new Form1());
    }
}

Мой вопрос сейчас таков: правильное ли мое мышление теперь с точки зрения выигрышных форм? Кажется, это имеет больше смысла, изменив мой подход к "повышению" цепочки и решив из Program.cs??

Во-вторых, и я не уверен, что это вообще требует нового вопроса, пожалуйста, посоветуйте мне, так как я НУБ.

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

Моя идея:

BootStrap Container.Register ();

Интерфейс и реализация фабрики: давайте добавим некоторые дополнительные параметры, так как я хочу знать, является ли это правильным / лучшим способом сделать это?

public interface IFileViewerFactory
{
    ILoader GetFileViewer(string FileName, string Directory = null, bool CreatingDirectory = false);
}

public class FileViewerFactory:IFileViewerFactory
{
    public FileViewerFactory() { }

    public ILoader GetFileViewer(string FileName, string Directory = null, bool CreatingDirectory = false)
    {
        if (CreatingDirectory == false)
        {
            if (Directory == null)
                return new FileViewByFileName(FileName);
            else
                return new FileViewByDirectoryName(Directory, FileName);
        }
        else
            return new FileViewByDirectoryNameCreateDirectoryOptional(Directory, FileName, CreatingDirectory);
    }
}

Форма:

public IFileViewerFactory FileViewerFactory { get; set; }

Нажатие кнопки:

ILoader FileLoader = FileViewerFactory.GetFileViewer(TxtBoxFileName.Text);

Или же:

ILoader FileLoader = FileViewerFacotry.GetFileViewer(TxtBoxFileName.Text,TxtBoxDirectory.Text);

Итак, чтобы закончить, мои вопросы:

  1. Является ли мой новый способ мышления "более высокого уровня" и загрузки из Program.cs теперь правильным
  2. Как я могу обработать необязательные параметры в LightInject
  3. Как я правильно настроил свой завод?
  4. Давайте забудем о Беглости с завода и просто попробуем поработать над механикой из вопросов:)

0 ответов

Я знаю, что уже поздно отвечать на вопрос, которому больше года, но позвольте мне попробовать.

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

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

Исторически одним из возможных подходов был шаблон Service Locator. Локатор спускается до самого дна стека и оттуда предлагает сервис, который разрешает зависимости. Таким образом, он доступен везде в стеке.

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

Окончательное решение называется Local Factory (он же Dependency Resolver), и оно заботится о создании лишь нескольких своих зависимых сервисов. Хитрость в том, чтобы в вашем приложении было несколько локальных фабрик.

Типичная установка такова. Предположим, есть сборка, назовите ее A, который клиент будет использовать для получения экземпляра IServiceA, Сборка содержит только два:

  • интерфейс (обязательство) сервиса - IServiceA
  • локальные фабричные клиенты будут использовать для получения экземпляров службы

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

// Assembly A

public interface IServiceA
{
   ...
}

public class ServiceAFactory
{
    private static Func<IServiceA> _provider;

    public static void SetProvider( Func<IServiceA> provider )
    {
        _provider = provider;
    }

    public IServiceA Create()
    {
        return _provider();
    }
}

у провайдера здесь есть функциональный контракт, но он также может быть выражен как интерфейс

И все, хотя на данный момент на фабрике нет реализации, клиентский код неожиданно становится очень стабильным:

// client code to obtain IServiceA
var serviceA = new ServiceAFactory().Create();

Обратите внимание еще раз, как автономно эта сборка A является. У него нет других ссылок, тем не менее, он предлагает чистый способ получения экземпляров вашего сервиса. Другие сборки могут ссылаться на эту сборку без каких-либо дополнительных ссылок.

Затем наступает корень композиции.

В самом верху вашей стопки ваша основная сборка ссылается на сборку A и некоторые другие сборки, давайте назовем это AImpl который содержит возможную реализацию интерфейса службы.

технически реализация сервиса может быть в той же сборке, что и интерфейс, но это только упрощает

Корень композиции создает поставщика фабрики, делегируя фабричный метод в стеке сборке. A

 // Composition Root in the top level module
 // Both assemblies
 //    * A     that contains IServiceA
 //    * AImpl that contains an implementation, ServiceAImpl
 // are referenced here 
 public void CompositionRoot()
 {
      ServiceAFactory.SetProvider( () =>
         {
             return new ServiceAImpl();
         } );
 }

Отныне провайдер настроен, и весь клиентский код в стеке, который использует фабрику, может успешно получать экземпляры.

Корень композиции также предоставляет все другие реализации других локальных фабрик. Затем в корне композиции есть несколько настроек:

    SomeLocalFactoryFromAssemblyA.SetProvider( ... );
    AnotherLocalFactoryFromAssemblyB.SetProvider( .... );
    ...

Где твой контейнер?

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

 public void CompositionRoot()
 {
      var container = new MyFavouriteContainer();
      container.Register<IServiceA, ServiceAImpl>(); // create my registrations

      ServiceAFactory.SetProvider( () =>
         {
             // this advanced provider uses the container
             // this means the implementation, the ServiceAImpl,
             // can have possible further dependencies that will be
             // resolved by the container
             container.Resolve<IServiceA>();
         } );
 }

Это самая чистая установка, которую я знаю. Он имеет все желаемые функции:

  • это четко разделяет проблемы
  • клиент на самом деле не нуждается ни в каких других зависимостях, кроме контракта на обслуживание и завода
  • клиент даже не знает, есть или будет контейнер, на самом деле клиенту все равно
  • в тестовой среде провайдеры могут быть легко настроены без какого-либо контейнера, чтобы обеспечить статические проверки ваших услуг
  • Корень композиции - это настоящий композитор - это единственное место в вашем коде, где три: интерфейсы, реализации и контейнер встречаются вместе
Другие вопросы по тегам