问题描述
我被要求为一个应用程序创建一系列报告,并且与往常一样,我正在寻找减少编写代码量的方法.我已经开始尝试提出最简单的方法来请求一份报告.这就是我的想象:
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:
- 使用一些标识符通用类(接口)代替枚举值.要将其用作字典中的键,您还必须具有该类的一些非泛型基础.
- 具有一些带有上述标识符类的静态类作为特定的静态属性.
- 将静态类属性中的值用作
ReportGenerator
类中的键.
- 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.
- Have some static class with aforementioned identifier classes as specific static properties.
- 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 :
这篇关于如何编写完全通用的此类并根据一个请求返回不同的响应?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!