Заменить factory на AutoFac

Я привык создавать свои собственные фабрики, как показано (это упрощено для иллюстрации):

public class ElementFactory
{
    public IElement Create(IHtml dom)
    {
        switch (dom.ElementType)
        {
            case "table":
                return new TableElement(dom);
            case "div":
                return new DivElement(dom);
            case "span":
                return new SpanElement(dom);
        }
        return new PassthroughElement(dom);
    }
}

Я, наконец, обойдусь использованием контейнера IoC (AutoFac) в своем текущем проекте, и мне интересно, есть ли какой-то волшебный способ добиться того же самого элегантного с AutoFac?

Ответы

Ответ 1

Короткий ответ: Да.

Более длинный ответ: во-первых, в простых случаях, когда класс Foo зарегистрирован как реализация для IFoo, параметры конструктора или свойства типа Func<IFoo> будут автоматически разрешаться Autofac без дополнительной проводки. Autofac будет вводить делегат, который в основном выполняет container.Resolve<IFoo>() при вызове.

В более сложных случаях, подобных вашим, где точное конкретирование возвращается на основе входных параметров, вы можете сделать одну из двух вещей. Во-первых, вы можете зарегистрировать метод factory в качестве возвращаемого значения для обеспечения параметризованного разрешения:

builder.Register<IElement>((c, p) => {
    var dom= p.Named<IHtml>("dom");
    switch (dom.ElementType)
    {
        case "table":
            return new TableElement(dom);
        case "div":
            return new DivElement(dom);
        case "span":
            return new SpanElement(dom);
    }
    return new PassthroughElement(dom);
  });

//usage
container.Resolve<IElement>(new NamedParameter("dom", domInstance))

Это не безопасно для типов (domInstance не будет проверяться компилятором, чтобы гарантировать его IHtml), и не очень чистый. Вместо этого другим решением является фактически зарегистрировать метод factory как Func:

builder.Register<Func<IHtml, IElement>>(dom =>
{
    switch (dom.ElementType)
    {
        case "table":
            return new TableElement(dom);
        case "div":
            return new DivElement(dom);
        case "span":
            return new SpanElement(dom);
    }
    return new PassthroughElement(dom);
});


public class NeedsAnElementFactory //also registered in AutoFac
{
    protected Func<IHtml,IElement> CreateElement {get; private set;}

    //AutoFac will constructor-inject the Func you registered
    //whenever this class is resolved.
    public NeedsAnElementFactory(Func<IHtml,IElement> elementFactory)
    {
        CreateElement = elementFactory;
    }  

    public void MethodUsingElementFactory()
    {
        IHtml domInstance = GetTheDOM();

        var element = CreateElement(domInstance);

        //the next line won't compile;
        //the factory method is strongly typed to IHtml
        var element2 = CreateElement("foo");
    }
}

Если вы хотите сохранить код в ElementFactory вместо того, чтобы поместить его в модуль Autofac, вы можете сделать статический метод factory и зарегистрировать его (это особенно хорошо работает в вашем случае, потому что ваш метод factory тривиально выполнен статический):

public class ElementFactory
{
    public static IElement Create(IHtml dom)
    {
        switch (dom.ElementType)
        {
            case "table":
                return new TableElement(dom);
            case "div":
                return new DivElement(dom);
            case "span":
                return new SpanElement(dom);
        }
        return new PassthroughElement(dom);
    }
}

...

builder.Register<Func<IHtml, IElement>>(ElementFactory.Create);