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