Replace factory with AutoFac

2019-02-01 10:52发布

问题:

I'm accustomed to creating my own factories as shown (this is simplified for illustration):

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

I'm finally getting around to using an IoC container (AutoFac) in my current project, and I'm wondering is there some magic way of achieving the same thing elegantly with AutoFac?

回答1:

Short answer: Yes.

Longer answer: First, in simple cases where a class Foo is registered as the implementation for IFoo, constructor parameters or properties of type Func<IFoo> will be resolved automatically by Autofac, with no additional wiring needed. Autofac will inject a delegate that basically executes container.Resolve<IFoo>() when invoked.

In more complex cases like yours, where the exact concretion returned is based on input parameters, you can do one of two things. First, you can register a factory method as its return value to provide a parameterized resolution:

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

This isn't type-safe (domInstance won't be compiler-checked to ensure it's an IHtml), nor very clean. Instead, another solution is to actually register the factory method as a 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");
    }
}

If you wanted to keep the code in ElementFactory instead of putting it in the Autofac module, you could make the factory method static and register that (this works especially well in your case because your factory method is trivially made static):

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