使用依赖注入进行基于条件的实例化

huangapple go评论74阅读模式
英文:

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 &quot;AR&quot;:
                         reportGenerator = new ARReportGenerator();
                         break;
               case &quot;AP&quot;:
                         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&lt;IReportGenerator, APReportGenerator&gt;();
 services.AddScoped&lt;IReportGenerator, ARReportGenerator&gt;();

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&lt;APReportGenerator&gt;();
services.AddScoped&lt;ARReportGenerator&gt;();

services.AddScoped&lt;Func&lt;string, IReportGenerator&gt;&gt;(sp =&gt; keyStr =&gt; keyStr switch
{
    &quot;AR&quot; =&gt; sp.GetRequiredService&lt;ARReportGenerator&gt;(),
    &quot;AP&quot; =&gt; sp.GetRequiredService&lt;APReportGenerator&gt;(),
    _ =&gt; throw new InvalidOperationException()
});

在控制器中:

public class AccountController : Controller
{
    private readonly IReportGenerator reportGeneratorFactory;
    public AccounterController(Func&lt;string, IReportGenerator&gt; 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&lt;APReportGenerator&gt;();
services.AddScoped&lt;ARReportGenerator&gt;();

services.AddScoped&lt;Func&lt;string, IReportGenerator&gt;&gt;(sp =&gt; keyStr =&gt; keyStr switch
{
    &quot;AR&quot; =&gt; sp.GetRequiredService&lt;ARReportGenerator&gt;(),
    &quot;AP&quot; =&gt; sp.GetRequiredService&lt;APReportGenerator&gt;(),
    _ =&gt; throw new InvalidOperationException()
});

And in controller:

public class AccountController : Controller
{
    private readonly IReportGenerator reportGeneratorFactory;
    public AccounterController(Func&lt;string, IReportGenerator&gt; 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 =&gt; &quot;AP&quot;;

    public void Process(ActivityReport activityReport) { }
}

And create a factory that stores all registered generators with their prefix:

internal class ReportGeneratorFactory : IReportGeneratorFactory
{
    private readonly Dictionary&lt;string, IReportGenerator&gt; _generators;

    public ReportFactory(IEnumerable&lt;IReportGenerator&gt; generators)
    {
        _generators = generators.ToDictionary(g =&gt; g.Prefix, g =&gt; g);
    }

    public IReportGenerator GetReportGenerator(string prefix)
    {
        return _generators[prefix];
    }
}

The trick here is to accept an IEnumerable&lt;IReportGenerator&gt;, which causes the Microsoft DI to inject all registered IReportGenerators.

You register this as such:

services.AddScoped&lt;IReportGenerator, APReportGenerator&gt;();
services.AddScoped&lt;IReportGenerator, ARReportGenerator&gt;();

services.AddScoped&lt;IReportGeneratorFactory, ReportGeneratorFactory&gt;();

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.

huangapple
  • 本文由 发表于 2023年4月20日 01:29:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76057311.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定