减少增加的构造函数服务参数

Reduce increasing Constructor service parameters

我对使用 Autofac 还是个新手,我对正在使用的构造函数注入方法感到困扰。场景如下:

我目前有两个 class 继承了 IForms 接口。每个 classes 都有自己的界面

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

public interface IFormA : IForms
{ }

public interface IFormB : IForms
{ }

现在我有一个 class 来处理这个问题,就像这样:

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

现在可能还会有 2 个表单(例如 FormCFormDFormE)。这里会发生的是 ApplicationForms 构造函数中将多出 3 个构造函数参数。

有没有办法把所有的构造函数参数合并成一个参数?看得出最后肯定很难看

既然有一个通用的IForms接口,那么可以注入一个枚举。

这看起来很适合策略模式。

我建议重构基本接口,以便能够识别它是什么类型的表单

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

并更新依赖 class

public class ApplicationForms : IApplicationForms {
    private readonly IEnumerable<IForms> forms;

    public ApplicationForms(IEnumerable<IForms> forms) {
        this.forms = forms;
    }

    public void SubmitApplicationForm(FormData data) {
        var form = forms.FirstOrDefault(_ => _.FormType == data.FormType);
        if(form != null)
            form.CreateNewForm(data);

        //...
    }
}

这假定在注册派生接口时,您将其与基础 IForms 接口相关联。

var builder = new ContainerBuilder();
builder.RegisterType<FormA>().As<IForms>();
builder.RegisterType<FormB>().As<IForms>();
builder.RegisterType<FormC>().As<IForms>();

//...

现在无论添加多少表单,class 都可以不加修改地执行。

您描述的问题是您有很多表单,但在运行时您需要一个特定的表单,因此您不想注入所有表单。这可能是一个抽象工厂的好场景。

我们经常将工厂表示为具有单一方法的接口,但我们也可以使用委托来实现:

public delegate IForm GetFormByTypeFunction(FormType formType);

现在你的 class 看起来像这样:

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

现在的问题是如何实现工厂。它可能仍然有一个 switch 语句或一些看起来不太优雅的东西,但没关系。工厂的要点是,无论它如何工作,创建 and/or 选择实现的业务都从依赖于实现的 class 中移出。

您可以像这样使用 Autofac 注册委托:

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

现在您不需要预先解析所有 IForm 实现,因为一旦您知道自己想要哪个实现,就可以直接从容器中解析您想要的实现。

这看起来 "wrong" 因为您是从容器中解析的。但是您没有从容器中直接 解析。你依赖于一家工厂。该工厂可以替换为任何其他实现,这意味着您的 class 不依赖于容器。

这种工厂也非常容易模拟。从技术上讲,它甚至不是模拟。它只是 returns 模拟工厂的一个实现。

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