Замените завод на AutoFac

#c# #.net #design-patterns #dependency-injection #autofac

#c# #autofac #factory-шаблон

Вопрос:

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

 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. В чем преимущество замены этой фабрики на Autofac?

2. Извините, как я упоминал в своем комментарии к ответу КейтсА, я надеялся, что будет какой-нибудь AutoFac voodoo, который позволит мне избежать переключения. В чем преимущество отказа от переключения? Я не знаю… Мне просто, кажется, внушили отвращение к ним : (

3. IoC — это не волшебная палочка. Вы можете использовать именованные регистрации, и вы могли бы удалить переключатель, но в конце концов всегда будет какая-то логика переключения. Мой совет: используйте factory для него и зарегистрируйте factory в Autofac.

Ответ №1:

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

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

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

 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 как функции:

 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, вы могли бы сделать заводской метод статичным и зарегистрировать его (это особенно хорошо работает в вашем случае, потому что ваш заводской метод тривиально сделан статичным):

 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);
  

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

1. Спасибо, это действительно полезно! Я отчасти надеялся, что AutoFac поможет мне избежать уродливого оператора switch, но вот оно, уставилось на меня, практически издеваясь надо мной, во всех трех ваших блоках кода 🙂

2. Ну, также можно использовать «регистрации с ключом», указав каждое из имен типа элемента в качестве ключа для метода разрешения для соответствующего типа объекта. Сложность в этой ситуации заключается в том, что не все возможные возвращаемые типы связаны с ключом (у вас есть регистр по умолчанию), а родительский элемент IHtml имени элемента необходим по другим причинам. Итак, казалось проще всего просто взять ваш рабочий код и настроить его так, чтобы AutoFac мог его использовать.

3. Регистрация не компилируется: builder.Register<Func<IHtml, IElement>>(ElementFactory.Create); я бы ожидал, что это будет выглядеть примерно так: builder.Register<Func<IHtml, IElement>>((context, parameters) => (html => ElementFactory.Create(html)));

4. Или еще проще: context => builder.Register<Func<IHtml, IElement>>(context => ElementFactory.Create) ;