Страницы

Поиск по вопросам

воскресенье, 15 марта 2020 г.

Внедрение списка зависимостей с помощью контейнера Castle.Windsor

#c_sharp #dependency_injection


В качестве IoC-контейнера используется Castle.Windsor.

Корневой элемент приложения - планировщик Scheduler, который выполняет в определенной
последовательности различные задания. Задания реализуют интерфейс IWorker, поэтому
я решил внедрить(inject) List в Scheduler(список заданий). Впоследствии, планируется
много различных типов заданий, сейчас их два: Cleaner и Writer. Cleaner нужен в одном
экземпляре, используем Синглтон. 
Необходимо правильно внедрить N экземпляров Writer, где N - количество XML файлов
в поддиректории приложения. Каждый из XML-файлов надо десериализовать и передать в
качестве входного параметра каждому Writerу. Т.е. надо внедрить в Schedulerимплементацию
для каждого XML. Как это правильно реализовать? Вообще какой паттерн используется в
таком случае? Фабрика?

Конфигурация контейнера Castle.Windsor:

var container = new WindsorContainer();
// Для внедрения List
container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true));
// Регистрация Scheduler
container.Register(CastleRegistration.Component.For().ImplementedBy);
// Регистрация Workers...
container.Register(CastleRegistration.Component.For().ImplementedBy());
container.Register(CastleRegistration.Component.For().ImplementedBy());
// ...количество зависит от количества файлов + каждому надо передать десериализованный
XML в качестве параметра
container.Register(CastleRegistration.Component.For().ImplementedBy());
// Забираем Scheduler
var sched = container.Resolve();


Scheduler:

public class Scheduler : IScheduler
{
    public IList Workers { get; set; }

    public Scheduler(IList workers)
    {
        Workers = workers;
    }
}


Writer:

public class Writer : IWorker
{
    public string Source { get; set; }

    public Writer()
    {
    }

    public Writer(string source)
    {
        Source = source;
    }
}


Cleaner:

public class Cleaner : IWorker
{
    public Cleaner()
    {
    }
}


UPDATE:
Дополнительный вопрос:

Содержимое Xml-файлов переехали в одну из таблиц БД. В Scheduler (корневой компонент)
нужно внедрить по Writerу с параметром на каждую запись в таблице. Из полезного совета
в ответе(Andrew Prigorshnev), следует, что обращаться к БД будет правильным после настройки
контейнера и получения из него корневого компонента.

С точки зрения архитектуры, правильным ли будет внедрить в Scheduler компоненты:
WriterFactoryи Repository(слой доступа к БД), после чего получить экземпляр Scheduler,
затем в нем с помощью Repository получить список параметров, а потом сделать на каждый
параметр по Writerу с помощью WriterFactory?
    


Ответы

Ответ 1



Насколько я понимаю в наиболее общем виде вопрос состоит в следующем: как правильно внедрить список зависимостей с помощью контейнера Castle Windsor? Ответ следующий. Сам подход с внедрением списка через конструктор вполне корректный и Castle Windsor умеет работать с такими сценариями. Чтобы внедрить список зависимостей надо сделать следующее: Зарегистрировать в контейнере резолвер списков. Вы это уже сделали в строчке container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true)); Зарегистрировать в контейнере несколько компонентов, которые должны быть внедрены в виде списка. При этом если вы внедряете список типа IList контейнер добавит в список все компоненты типа IWorker, которые вы зарегистрируете. Это вы сделали частично. В вашем примере не учтено, что когда вы регистрируете несколько одинаковых компонентов они должны быть именованными (делается с помощью метода .Named()), и по всей видимости вы не знаете как внедрить строку в конструктор компонента - это делается с помощью метода .DependsOn() Таким образом правильная реализация вашей задачи должна выглядеть вот так: var container = new WindsorContainer(); // Для внедрения List container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true)); // Регистрация Scheduler container.Register(Component.For().ImplementedBy()); // Writers container.Register(Component .For() .ImplementedBy() .DependsOn(Dependency.OnValue("source", "files/first.xml")) .Named("firstWriter")); container.Register(Component .For() .ImplementedBy() .DependsOn(Dependency.OnValue("source", "files/second.xml")) .Named("secondWriter")); container.Register(Component .For() .DependsOn(Dependency.OnValue("source", "files/third.xml")) .ImplementedBy() .Named("thirdWriter")); // Cleaner container.Register(Component.For().ImplementedBy()); // Забираем Scheduler var scheduler = container.Resolve(); И еще несколько замечаний, касающихся конкретно вашей реализации: Вы хотите передавать в воркеры xml, но это плохая идея. Тогда вам придется прочитать его из файла прямо в корне компоновки, в том месте где вы настраиваете контейнер. Но чтение из файла это отдельная задача, никак не связанная с разрешением зависимостей, поэтому делать ее в корне компоновки не стоит. Пусть этим занимается один из ваших компонентов. В связи с этим, в примере кода выше вы можете увидеть что во Writer'ы передается путь к файлу, а не его содержимое. Если файлы, которые вы обрабатывате меняются редко и их имена и количество известны на этапе написания кода, пример приведенный выше может вас полностью устроить. Но если это не так, надо сделать еще немного дополнительной работы, и как вы правильно предположили, надо использовать фабрики. Возможное решение здесь следующее. Надо добавить новую абстракцию - поставщик воркеров, который будет представлять собой фабрику, создающую воркеры: public interface IWorkersProvider { IEnumerable GetWorkers(); } Поставщик Cleaner'ов будет просто возвращать один Cleaner. А поставщик Writer'ов может получать как аргумент конструктора путь к папке, в которой надо искать файлы, и затем в методе GetWorkersсоздавать по одному воркеру на каждый найденный файл: public class WritersProvider : IWorkersProvider { public WritersProvider(string folderPath) { } public IEnumerable GetWorkers() { // здесь создаем по одному воркеру на каждый найденный файл } } При такой архитектуре ваш Sheduler должен получать вместо списка воркеров, список поставщиков воркеров: public class Scheduler : IScheduler { public Scheduler(IList providers) { // здесь надо опросить все поставщики, чтобы сформировать // список воркеров } } Регистрация такой конфигурации в Castle Windsor делается с помощью тех же методов, что и регистрация в первом примере, поэтому не буду здесь ее описывать. UPDATE: Ответ на дополнительный вопрос Лучше сделать так: В Scheduler внедрить список поставщиков воркеров (IEnumerable), как описано выше Внедрить Repository в конструктор WritersProvider и уже во WritersProviderопрашивать базу данных Идея здесь вот в чем. Каждый класс должен выполнять одну единственную задачу. Задача Scheduler-а взять готовый список воркеров и запустить их. Задача поставщиков воркеров - создать воркеры на основе какого-то правила (из xml-файлов, таблиц бд и т.д.). Разделив обязанности таким образом, вы получите код, который легко модифицировать. Если вы захотите поменять что-то в логике запуска воркеров, вы прийдете и поменяете только один класс - Scheduler, при этом вы будете уверены, что вы никак не сломали логику создания воркеров, потому что эту работу выполняют совсем другие классы. Потенциально у вас может быть множество поставщиков воркеров, работающих по разным правилам. Один поставщик может всегда возвращать один и тот же жестко закодированный воркер, второй создавать по воркеру на каждый xml-файл в какой-нибудь директории, третий создавать воркеры на основе таблицы в базе данных, четвертый получать список воркеров, которые надо создать, у удаленного сервиса и т.д. Более того, в процессе развития проекта у поставщика воркеров может меняться правило, по которому он создает воркеры (у вас это уже произошло - вместо xml-файлов у вас теперь таблица в бд.) Если поставщику воркеров для выполнения его работы нужен доступ к бд, внедряйте Repository прямо в поставщик. Если потом это правило изменится, прийдете и поменяете только конкретный поставщик, не трогая другие поставщики и не трогая Scheduler. Вариант с фабрикой похуже, но тоже годится. Но если будете внедрять фабрику воркеров то Repository внедряйте уже в нее, а не в Scheduler. Фабрика хуже, потому что, когда количество воркеров вырастет, вы все равно будете вынуждены разнести создание воркеров разных типов по отдельным классам, иначе фабрика будет слишком большой и запутанной. В итоге вы все равно создадите несколько фабрик (что полность аналогично моему варианту со списком поставщиков воркеров, отличие только в названии, фабрика и поставщик в данном случае одно и то же).

Комментариев нет:

Отправить комментарий