英文:
Using dependency injection for condition based instantiation
问题
我有一个接口和两个类
public interface IReportGenerator
{
void Process(ActivityReport activityReport);
}
class APReportGenerator : IReportGenerator
{
void Process(ActivityReport activityReport);
}
class ARReportGenerator : IReportGenerator
{
void Process(ActivityReport activityReport);
}
在我的控制器中,基于用户输入,我实例化子类如下
public class AccountController : Controller
{
private IReportGenerator reportGenerator;
[HttpPost]
public IActionResult Submit(ActivityReport activityReport)
{
switch(activityReport.Type)
{
case "AR":
reportGenerator = new ARReportGenerator();
break;
case "AP":
reportGenerator = new APReportGenerator();
break;
}
reportGenerator.Process(activityReport);
...
}
}
有人能帮我重写代码,不使用new吗?
在startup.cs中,我可以像这样吗?
services.AddScoped<IReportGenerator, APReportGenerator>();
services.AddScoped<IReportGenerator, ARReportGenerator>();
在控制器中?
public class AccountController : Controller
{
private readonly IReportGenerator reportGenerator;
public AccounterController(IReportGenerator reportGenerator)
{
this.reportGenerator = reportGenerator;
}
抱歉,我在这之后卡住了,不知道这样对还是不对。
英文:
I have an interface and two classes
public interface IReportGenerator
{
void Process(ActivityReport activityReport);
}
class APReportGenerator : IReportGenerator
{
void Process(ActivityReport activityReport);
}
class ARReportGenerator : IReportGenerator
{
void Process(ActivityReport activityReport);
}
In my controller, based on user input, I am instantiating the child classes like this
public class AccountController : Controller
{
private IReportGenerator reportGenerator;
[HttpPost]
public IActionResult Submit(ActivityReport activityReport)
{
switch(activityReport.Type)
{
case "AR":
reportGenerator = new ARReportGenerator();
break;
case "AP":
reportGenerator = new APReportGenerator();
break;
}
reportGenerator.Process(activityReport);
...
}
}
Can someone please help me rewrite the code without the use of new?
In startup.cs, can I have something like this?
services.AddScoped<IReportGenerator, APReportGenerator>();
services.AddScoped<IReportGenerator, ARReportGenerator>();
And in controller?
public class AccountController : Controller
{
private readonly IReportGenerator reportGenerator;
public AccounterController(IReportGenerator reportGenerator)
{
this.reportGenerator = reportGenerator;
}
Sorry but I am stuck after this and dont know if this is correct or wrong.
答案1
得分: 4
以下是您要翻译的内容:
在某些容器中,可以使用keyed/named注册来实现这一点,但内置的注册不支持此功能。因此,通常的方法是使用工厂。使用 Func
委托的简单方法可能如下所示:
services.AddScoped<APReportGenerator>();
services.AddScoped<ARReportGenerator>();
services.AddScoped<Func<string, IReportGenerator>>(sp => keyStr => keyStr switch
{
"AR" => sp.GetRequiredService<ARReportGenerator>(),
"AP" => sp.GetRequiredService<APReportGenerator>(),
_ => throw new InvalidOperationException()
});
在控制器中:
public class AccountController : Controller
{
private readonly IReportGenerator reportGeneratorFactory;
public AccounterController(Func<string, IReportGenerator> reportGeneratorFactory)
{
this.reportGeneratorFactory = reportGeneratorFactory;
}
[HttpPost]
public IActionResult Submit(ActivityReport activityReport)
{
var generator = reportGeneratorFactory(activityReport.Type);
// 使用 generator
}
}
附言:
您的方法将导致始终使用 ARReportGenerator
(因为它是最后添加的)。
英文:
In some containers this can be achieved with keyed/named registrations but build-in one does not support this. So the usual approach is to use factory. Simple one using Func
delegate can look something like the following:
services.AddScoped<APReportGenerator>();
services.AddScoped<ARReportGenerator>();
services.AddScoped<Func<string, IReportGenerator>>(sp => keyStr => keyStr switch
{
"AR" => sp.GetRequiredService<ARReportGenerator>(),
"AP" => sp.GetRequiredService<APReportGenerator>(),
_ => throw new InvalidOperationException()
});
And in controller:
public class AccountController : Controller
{
private readonly IReportGenerator reportGeneratorFactory;
public AccounterController(Func<string, IReportGenerator> reportGeneratorFactory)
{
this.reportGeneratorFactory = reportGeneratorFactory;
}
[HttpPost]
public IActionResult Submit(ActivityReport activityReport)
{
var generator = reportGeneratorFactory(activityReport.Type);
// use generator
}
}
P.S.
Your approach will lead to ARReportGenerator
always being used (because it was added last).
答案2
得分: 2
以下是您提供的内容的中文翻译:
在这种情况下,适用Java原则,即:当有疑问时,请使用工厂:
public interface IReportGenerator
{
string Prefix { get; }
void Process();
}
public interface IReportGeneratorFactory
{
IReportGenerator GetReportGenerator(string prefix);
}
然后,让每个报告生成器报告它可以处理的前缀:
internal class APReportGenerator : IReportGenerator
{
public string Prefix => "AP";
public void Process(ActivityReport activityReport) { }
}
并创建一个存储所有已注册生成器及其前缀的工厂:
internal class ReportGeneratorFactory : IReportGeneratorFactory
{
private readonly Dictionary<string, IReportGenerator> _generators;
public ReportFactory(IEnumerable<IReportGenerator> generators)
{
_generators = generators.ToDictionary(g => g.Prefix, g => g);
}
public IReportGenerator GetReportGenerator(string prefix)
{
return _generators[prefix];
}
}
这里的技巧是接受一个IEnumerable<IReportGenerator>
,这会导致Microsoft DI注入所有已注册的IReportGenerator
。
您可以这样注册它:
services.AddScoped<IReportGenerator, APReportGenerator>();
services.AddScoped<IReportGenerator, ARReportGenerator>();
services.AddScoped<IReportGeneratorFactory, ReportGeneratorFactory>();
然后,在需要生成器的地方,您注入工厂,根据报告类型获取相关的生成器:
public class AccountController : Controller
{
private readonly IReportGeneratorFactory _reportGeneratorFactory;
public AccounterController(IReportGeneratorFactory reportGeneratorFactory)
{
_reportGeneratorFactory = reportGeneratorFactory;
}
[HttpPost]
public IActionResult Submit(ActivityReport activityReport)
{
var generator = _reportGeneratorFactory.GetReportGenerator(activityReport.Type);
generator.Process();
}
}
此代码仍然需要处理两种特殊情况,即多个生成器报告相同的前缀和请求生成器的前缀未知的情况。所示的代码在这两种情况下都会引发异常。
英文:
The Java doctrine applies in this case, being: when in doubt, use a factory:
public interface IReportGenerator
{
string Prefix { get; }
void Process();
}
public interface IReportGeneratorFactory
{
IReportGenerator GetReportGenerator(string prefix);
}
You then let each report generator report which prefixes it can handle:
internal class APReportGenerator : IReportGenerator
{
public string Prefix => "AP";
public void Process(ActivityReport activityReport) { }
}
And create a factory that stores all registered generators with their prefix:
internal class ReportGeneratorFactory : IReportGeneratorFactory
{
private readonly Dictionary<string, IReportGenerator> _generators;
public ReportFactory(IEnumerable<IReportGenerator> generators)
{
_generators = generators.ToDictionary(g => g.Prefix, g => g);
}
public IReportGenerator GetReportGenerator(string prefix)
{
return _generators[prefix];
}
}
The trick here is to accept an IEnumerable<IReportGenerator>
, which causes the Microsoft DI to inject all registered IReportGenerator
s.
You register this as such:
services.AddScoped<IReportGenerator, APReportGenerator>();
services.AddScoped<IReportGenerator, ARReportGenerator>();
services.AddScoped<IReportGeneratorFactory, ReportGeneratorFactory>();
Then where you need a generator, you inject the factory instead, and based on the report type, obtain the relevant generator:
public class AccountController : Controller
{
private readonly IReportGeneratorFactory _reportGeneratorFactory;
public AccounterController(IReportGeneratorFactory reportGeneratorFactory)
{
_reportGeneratorFactory = reportGeneratorFactory;
}
[HttpPost]
public IActionResult Submit(ActivityReport activityReport)
{
var generator = _reportGeneratorFactory.GetReportGenerator(activityReport.Type);
generator.Process();
}
}
This code still needs to handle two exceptional cases, namely multiple generators reporting the same prefix and a generator being requested whose prefix isn't known. The code shown will throw an exception in both cases.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论