В качестве 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?
Ответ
Насколько я понимаю в наиболее общем виде вопрос состоит в следующем: как правильно внедрить список зависимостей с помощью контейнера 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
Фабрика хуже, потому что, когда количество воркеров вырастет, вы все равно будете вынуждены разнести создание воркеров разных типов по отдельным классам, иначе фабрика будет слишком большой и запутанной. В итоге вы все равно создадите несколько фабрик (что полность аналогично моему варианту со списком поставщиков воркеров, отличие только в названии, фабрика и поставщик в данном случае одно и то же).