如何编写完全通用的此类并根据一个请求返回不同的响应? [英] How can I write this class to be fully generic and return different responses according to one request?

查看:104
本文介绍了如何编写完全通用的此类并根据一个请求返回不同的响应?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我被要求为一个应用程序创建一系列报告,并且与往常一样,我正在寻找减少编写代码量的方法.我已经开始尝试提出最简单的方法来请求一份报告.这就是我的想象:

I was asked to create a series of reports for an application and as always, I'm looking for ways to reduce the amount of code written. I've started trying to come up with the easiest way to request a single report. Here's what I imagined:

var response = ReportGenerator.Generate(Reports.Report1);
//Reports would be an enum type with all of the available reports.

当我尝试设计它时,问题就出现了.每个报告都有不同的输入和输出.输入是报表所基于的一个或多个实体,输出是保存已处理数据的DTO.

As soon as I tried to design that, the problems appeared. Every report has a different input and output. The input being the entity (or entities) on which the report is based and the output being the DTO holding the processed data.

对此进行备份,我创建了此文件:

Backing this up, I created this:

// The interface for every report
public interface IReport<INPUT, OUTPUT>
{
  public OUTPUT GenerateReport(INPUT input);
}

// A base class for every report to share a few methods
public abstract class BaseReport<INPUT, OUTPUT> : IReport<INPUT, OUTPUT>
{
  // The method required by the IReport interface
  public OUTPUT GenerateReport(INPUT input)
  {
    return Process(input);
  }

  // An abstract method to be implemented by every concrete report
  protected abstract OUTPUT Process(INPUT input);
}

public class ConcreteReport : BaseReport<SomeEntity, SomeDto>
{
  protected override SomeDto Process(SomeEntity input)
  {
    return default(SomeDto);
  }
}

起初,我正在考虑让每份具体报告都指定负责确定其自身输入的逻辑.我很快看到,这会使我的课程难以测试.通过让报告请求 INPUT 泛型类型的实例,我可以模拟该对象并测试报告.

At first I was considering to have every concrete report to specify the logic responsible to determine its own input. I quickly saw that it would make my class less testable. By having the report request an instance of the INPUT generic type I can mock that object and test the report.

因此,我需要一种类来将报告(枚举值之一)与负责其生成的具体报告类联系起来.我正在尝试使用类似于依赖项注入容器的方法.这是我很难写的课.

So, what I need is some kind of class to tie a report (one of the enum values) to a concrete report class responsible for its generation. I'm trying to use an approach similar to a dependency injection container. This is the class I'm having trouble to write.

我将在下面用注释写出我所发现的问题(它在语法上不正确,这只是一个存根,因为我的问题恰恰是该类的实现),

I'll write below what I have with comments explainning the problems I've found (it's not supposed to be syntatically correct - it's just a stub since my problem is exactly the implementation of this class):

public class ReportGenerator
{
  // This would be the dictionary responsible for tying an enum value from the Report with one of the concrete reports.
  // My first problem is that I need to make sure that the types associated with the enum values are instances of the BaseReport class.
  private readonly Dictionary<Reports, ?> registeredReports;

  public ReportGenerator()
  {
    // On the constructor the dictionary would be instantiated...
    registeredReports = new Dictionary<Reports, ?>();

    // and the types would be registered as if in a dependency injection container.
    // Register(Reports.Report1, ConcreteReport);
    // Register(Reports.Report2, ConcreteReport2);
  }

  // Below is the most basic version of the registration method I could come up with before arriving at the problems within the method GenerateReport.

  // T repository              - this would be the type of the class responsible for obtainning the input to generate the report
  // Func<T, INPUT> expression - this would be the expression that should be used to obtain the input object

  public void Register<T, INPUT>(Reports report, Type reportConcreteType, T repository, Func<T, INPUT> expression)
  {
    // This would basically add the data into the dictionary, but I'm not sure about the syntax
    // because I'm not sure how to hold that information so that it can be used later to generate the report

    // Also, I should point that I prefer to hold the types and not instances of the report and repository classes.
    // My plan is to use reflection to instantiate them on demand.
  }

  // Based on the registration, I would then need a generic way to obtain a report.
  // This would the method that I imagined at first to be called like this:
  // var response = ReportGenerator.Generate(Reports.Report1);

  public OUTPUT Generate(Reports report)
  {
    // This surely does not work. There is no way to have this method signature to request only the enum value
    // and return a generic type. But how can I do it? How can I tie all these things and make it work?
  }
}

我可以看到它与报告接口或抽象类无关,但是我无法弄清楚实现.

I can see it is not tied with the report interface or abstract class but I can't figure out the implementation.

推荐答案

我不确定是否可以通过枚举实现这种行为,所以我可以为您提出以下解决方案:

I am not sure that it is possible to achieve such behaviour with enum, so I can propose you the following solution:

  1. 使用一些标识符通用类(接口)代替枚举值.要将其用作字典中的键,您还必须具有该类的一些非泛型基础.
  2. 具有一些带有上述标识符类的静态类作为特定的静态属性.
  3. 将静态类属性中的值用作ReportGenerator类中的键.
  1. Use some identifier generic class(interface) in place of enum values. To use it as key in dictionary you will also have to have some non-generic base for this class.
  2. Have some static class with aforementioned identifier classes as specific static properties.
  3. Use values from static class properties as keys in ReportGenerator class.

以下是必需的接口:

public interface IReportIdentifier
{

}

public interface IReportIdentifier<TInput, TOutput> : IReportIdentifier
{

}

public interface IReport<TInput, TOutput>
{
    TOutput Generate(TInput input);
}

这是静态的枚举"类:

public static class Reports
{
    public static IReportIdentifier<String, Int32> A
    {
        get { return null;}
    }

    public static IReportIdentifier<Object, Guid> B
    {
        get { return null; }
    }
}

这是ReportGenerator类:

And here is the ReportGenerator class:

public class ReportGenerator
{
    IDictionary<IReportIdentifier, Object> reportProducers = new Dictionary<IReportIdentifier, Object>();

    public void Register<TInput, TOutput>(IReportIdentifier<TInput, TOutput> identifier, IReport<TInput, TOutput> reportProducer)
    {
        reportProducers.Add(identifier, reportProducer);
    }

    public TOutput Generate<TInput, TOutput>(IReportIdentifier<TInput, TOutput> identifier, TInput input)
    {
        // Safely cast because it is this class's invariant.
        var producer = (IReport<TInput, TOutput>)reportProducers[identifier];
        return producer.Generate(input);
    }
}

如您所见,我们使用强制转换,但是它隐藏在Generate方法内部,如果Register方法是reportProducers词典的唯一访问点,则此强制转换不会失败.

As you see, we use cast but it is hidden inside the Generate method and if our Register method is the only access point to the reportProducers dictionary this cast will not fail.

以及 @CoderDennis

然后,您始终可以使用 T4 生成该静态类及其 静态属性,甚至可以创建一个扩展方法 从您的枚举中返回正确的IReportIdentifier.

Then you could always use T4 to generate that static class and its static properties and could even create an extension method that returns the proper IReportIdentifier from your enum.

这篇关于如何编写完全通用的此类并根据一个请求返回不同的响应?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆