减少增加的构造函数服务参数
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 个表单(例如 FormC
、FormD
、FormE
)。这里会发生的是 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);
我对使用 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 个表单(例如 FormC
、FormD
、FormE
)。这里会发生的是 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);