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.
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);
}
}
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.
Aucun commentaire:
Enregistrer un commentaire