Расскажу об интересном решении, которое недавно мне удалось придумать.
disclaimer: Речь на самом деле не про DI/IoC-контейнеры — это всего лишь контекст, в котором возникла задача.
Многие контейнеры поддерживают сразу два интерфейса конфигурирования — кодом/атрибутами и xml-ем.
Я пишу свой собственный DI-контейнер (самый лучший, естественно, но для данного поста это не важно). До недавнего времени его нельзя было конфигурировать через xml. Только кодом, примерно вот так:
var container = new Container(
c =>
{
c.ForPlugin<IComponent>()
.UsePluggable<Component1>()
.UseAutoFoundPluggables()
.DontUse<Component2>()
.DontUse<Component3>()
.DontUse<Component4>()
.ReusePluggable(ReusePolicy.Never);
c.ForPluggable<Component1>().DontUseIt();
c.ForPluggable<Component4>()
.ReuseIt(ReusePolicy.Never)
.UseConstructor(typeof(IComponent))
.Dependency("comp").UsePluggable<Component2>();
}
);
То есть конфигурирование — это просто последовательность вызовов методов "настрой то", "настрой сё".
Создавать для этого отдельный xml-интерфейс не хотелось. Это предполагало много тупой работы по дублированию имеющегося интерфейса, а в будущем много тупой работы по синхронизации двух интерфейсов.
Поэтому я придумал хитрый план — сделать универсальный xml-конфигуратор! Чтобы он ничего не знал про мой контейнер, а просто умел с помощью рефлексии превращать xml-элементы в вызовы методов указанного объекта.
С использованием такого конфигуратора приведенный выше пример конфигурирования можно переписать вот так:
var container = new Container(
c => XmlConfiguration.FromXmlFile("config.xml").ApplyConfigTo(с);
);
Вот с таким config.xml:
<?xml version="1.0" encoding="utf-8" ?>
<robocontainer>
<ForPlugin pluginType="RoboContainer.Tests.Configuration.IComponent, RoboContainer.Tests">
<UsePluggable pluggableType="RoboContainer.Tests.Configuration.Component1, RoboContainer.Tests"/>
<UseAutoFoundPluggables/>
<DontUse>
<pluggableTypes item="RoboContainer.Tests.Configuration.Component2, RoboContainer.Tests"/>
<pluggableTypes item="RoboContainer.Tests.Configuration.Component3, RoboContainer.Tests"/>
<pluggableTypes item="RoboContainer.Tests.Configuration.Component4, RoboContainer.Tests"/>
</DontUse>
<ReusePluggable reusePolicy="Never"/>
</ForPlugin>
<ForPluggable pluggableType="RoboContainer.Tests.Configuration.Component1, RoboContainer.Tests">
<DontUseIt/>
</ForPluggable>
<ForPluggable pluggableType="RoboContainer.Tests.Configuration.Component4, RoboContainer.Tests">
<ReuseIt reusePolicy="Never"/>
<UseConstructor>
<argsTypes item="RoboContainer.Tests.Configuration.IComponent, RoboContainer.Tests"/>
</UseConstructor>
<Dependency dependencyName="comp">
<UsePluggable pluggableType="RoboContainer.Tests.Configuration.Component2, RoboContainer.Tests"/>
</Dependency>
</ForPluggable>
</robocontainer>
Ещё раз подчеркну, что с помощью этого конфигуратора можно конфигурировать что угодно. Да хоть
Dictionary<string, int>
!
var d = new Dictionary<string, int>();
XmlConfiguration.FromXmlFile("config.xml").ApplyConfigTo(d);
<?xml version="1.0" encoding="utf-8" ?>
<dictionary>
<Add key="key1" value="42" />
<Add key="key2" value="3" />
<Add key="key3" value="5" />
<Add key="key4" value="7" />
</dictionary>
Реализация, как вы догадываетесь, совсем несложная. Но тем, что такая идея пришла мне в голову я немножко горжусь.
Впрочем, если реализация кому-то все же интересна, то вот она:
http://code.google.com/p/robo-container/source/browse/#svn/trunk/RoboContainer/RoboConfig
Она не сильно много умеет, но хорошо расширяема — можно брать за основу, если вдруг надо.
Два слова по поводу минусов. Подобное решение фактически устраняет дублирование интерфейсов (xml и кода). Как и любое дублирование, дублирование интерфейсов дает их изолированность. Иногда изолированность — это хорошее свойство (поменяли одно, второе не поломалось), иногда плохое (поменяли одно, про второе забыли). В моем случае — это единственный вид xml-конфигурирования, на который я был морально готов — никакого дублирования!