Search code examples
c#dependency-injectionautofacautofac-configuration

Reduce increasing Constructor service parameters


I'm still new to using Autofac and I'm bothered by the constructor injection method that I'm using. Here's the scenario:

I currently have two classes which inherits the IForms interface. Each of the classes has its own interface as well

public interface IForms 
{
    long CreateNewForm(FormData data);
    FormData RetrieveFormById(long id);
}

public interface IFormA : IForms
{ }

public interface IFormB : IForms
{ }

Now I have a class which handles this which is like this:

public class ApplicationForms : IApplicationForms
{
    private readonly IFormA _formA;
    private readonly IFormB _formB;

    public ApplicationForms(IFormA formA, IFormB formB)
    {
        _formA = formA;
        _formB = formB;
    }

    public void SubmitApplicationForm(FormData data)
    {
        switch(data.FormType)
        {
            case FormType.FormA:
                _formA.CreateNewForm(data);
                break;
            case FormType.FormB:
                _formB.CreateNewForm(data);
                break;
        }
    }
}

Now there's a possibility that there will be 2 more forms coming in (ex. FormC, FormD, FormE). What will happen here is that there will be 3 more constructor parameters in the ApplicationForms constructor.

Is there a way to like combine all the constructor parameters into one parameter? I can see that it will definitely look ugly in the end.


Solution

  • The problem you're describing is that you have many forms, but at runtime you need one specific form, and so you don't want to inject all of the forms. That might be a good scenario for an abstract factory.

    We often represent the factory as an interface with a single method, but we can also do it with a delegate:

    public delegate IForm GetFormByTypeFunction(FormType formType);
    

    Now your class looks like this:

    public class ApplicationForms : IApplicationForms
    {
        private readonly GetFormByTypeFunction _getFormByType;
    
        public ApplicationForms(GetFormByTypeFunction getFormByType)
        {
            _getFormByType = getFormByType;
        }
    
        public void SubmitApplicationForm(FormData data)
        {
            var form = _getFormByType(data.FormType);
            form.CreateNewForm(data);
        }
    }
    

    Now the question is how to implement the factory. It might still have a switch statement or something that doesn't seem too elegant, but that's okay. The point of the factory is that however it works, the business of creating and/or selecting an implementation is moved out of the class that depends on the implementation.

    You can register a delegate with Autofac like this:

    builder.Register<GetFormByTypeFunction>(context => formType =>
    {
        switch (formType)
        {
            case FormType.Type1:
            {
                return context.Resolve<FormOne>();
            }
            case FormType.Type2:
            {
                return context.Resolve<FormTwo>();
            }
            default:
                throw new InvalidOperationException("Unknown form type");
        }
    });
    

    Now you don't need to resolve all of the IForm implementations up front because you can resolve the one you want directly from the container once you know which one you want.

    That might seem "wrong" because you're resolving from the container. But you're not resolving directly from the container. You're depending on a factory. That factory can be replaced with any other implementation which means that your class does not depend on the container.

    This sort of factory is also super easy to mock. Technically it's not even a mock. It's just an implementation of the factory that returns a mock.

    var formMock = new Mock<IForm>();
    var factory = new GetFormByTypeFunction(formType => formMock.Object);