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);
Итак, чтобы закончить, мои вопросы:
- Является ли мой новый способ мышления "более высокого уровня" и загрузки из Program.cs теперь правильным
- Как я могу обработать необязательные параметры в LightInject
- Как я правильно настроил свой завод?
- Давайте забудем о Беглости с завода и просто попробуем поработать над механикой из вопросов:)
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>();
} );
}
Это самая чистая установка, которую я знаю. Он имеет все желаемые функции:
- это четко разделяет проблемы
- клиент на самом деле не нуждается ни в каких других зависимостях, кроме контракта на обслуживание и завода
- клиент даже не знает, есть или будет контейнер, на самом деле клиенту все равно
- в тестовой среде провайдеры могут быть легко настроены без какого-либо контейнера, чтобы обеспечить статические проверки ваших услуг
- Корень композиции - это настоящий композитор - это единственное место в вашем коде, где три: интерфейсы, реализации и контейнер встречаются вместе