3

I have a requirement where user selects a ReportType from a dropdown and hits download button. Based on his type chosen, the system should generate a report. Right now I have only report type that is QuoteReport. In future I will have other report types like PolicyReport,ClaimReport. Right now I have no idea what will be data-fields in these reports too, But "They all will have at least some common properties" such as ID, and Address

 public class QuoteReport
 {
  public String DeviceType { get; set; }
  public String ProductName { get; set; }
  public String Description { get; set; }
  public String ID { get; set; }
  public String Address { get; set; }     
 }

Now what I am doing is I send reporttype and parameters to fill the report and I have created a switch case to catch type of report being selected.

public string PrepareReport(string selectedReport, List<int> Ids)
{
string response = string.Empty;
try
{
    ReportTypeEnum reportTypeEnum;
    if (Enum.TryParse(selectedReport, out reportTypeEnum))
    {
        switch (reportTypeEnum)
        {
            case ReportTypeEnum.QuoteReport:
                response = CreateReportData(Ids,response);
                break;
            default:
                break;
        }
    }
}
catch (Exception exc)
{
    handleException(DOWNLOAD_REPORT, exc);
}
return response;
}

My method CreateReportData fills the fields of QuoteReport class from wcf.

 public string CreateReportData(List<int> Ids, string response)
{
List<QuoteReport> quoteReportList = new List<QuoteReport>();            
foreach (var Id in Ids)
{
    dynamic dynamicEntity;
    List<string> devices = proxy.GetData(Id);
    for (int i = 0; i < devices.Count; i++)
    {
        QuoteReport quoteReport = new QuoteReport();
        dynamicEntity = JObject.Parse(devices[i]);
        quoteReport.Type = dynamicEntity.DeviceTypeString;
        quoteReport.ProductName = dynamicEntity.ProductName;
        quoteReport.Description = dynamicEntity.Desc;
        quoteReport.ID = dynamicEntity.ID;
        assetReport.Address = dynamicEntity.Address;
         quoteReportList.Add(quoteReport );

    }
}
response = JsonConvert.SerializeObject(quoteReportList );
return response;
}

Now I am perplexed how can I make my code more generic. Or should I use some design patterns like Factory to make code adaptable for future needs? How can I make CreateReportData method generic so that it accepts any class type and fills it up?

  • What benefit does making this "more generic" bring? Maybe pulling the body of the inner loop into a String -> QuoteReport function is useful. – Caleth Jan 23 '17 at 9:38
3

The design pattern begging for attention here when you say "adaptable for future needs" is the strategy pattern. You could eliminate the switch, the enum, and likely PrepareReport if clicking that download button ran this code:

selectedReport.CreateReport(List<int> Ids);

What code that ran would depend entirely on the object currently in set in selectedReport. You could create any number of types of reports this way without having to change any existing code except the code that offers the list of report types in the gui. The way you have it each new type needs to be added in at least two places.

This change is actually one of Martin Fowler's original refactorings: Replace Conditional With Polymorphism.

You can implement it in several different ways but the strategy pattern is a good way to get polymorphism. The idea is to create code that doesn't need changes in many places to add a new report.

  • What would be the type of selectedReport ? And you would need to convert the dropdown entry to the corresponding report generator ? – la_urre Jan 23 '17 at 16:36
  • @la_urre The type would be IReportCreator. A tidy little interface that promises that the CreateReport() method will exist. – candied_orange Jan 23 '17 at 20:55
  • 1
    this approach worked perfectly for me @CandiedOrange – pankaj Jan 26 '17 at 10:17
0

Two things to worry about: state and behavior. Let's talk about state first.

If we take your plain English explanation I can translate into code.

All reports will have some common properties, like ID and Address

abstract class BaseReport
{
    string ID { get; set; }
    Address Address { get; set; }
}

In future I will have other report types like PolicyReport,ClaimReport.

class PolicyReport: BaseREport
{
    //Define additional fields here
}

class ClaimReport: BaseReport
{
    //Define additional fields here
}

Notice that in the classes that define specific reports, you don't need to declare ID and Address, because they are already in there as base class members.

So, that is state. What about behavior?

One thing you need to decide is whether these objects will be able to retrieve their own data. You could certainly write code inside each of them that gets the data needed to populate the report:

abstract class BaseReport
{
    abstract DataTable GetData();
    abstract void Render(DataTable data);

    public void Create()
    {
        var data = GetData(); 
        Render(data);
    }
}

then in the specific report you'd write

public class PolicyReport : BaseReport
{
    override public DataTable GetData()
    {
        var client = new SqlClient();
        return client.RunQuery("SELECT * FROM Policy");
    }

    override public void Render(DataTable data)
    {
        //Draw the report somehow
    }
}

Then to create a policy report you'd execute:

var policyReport = new PolicyReport();
policyReport.Create();

But if you want to separate the data retrieval from the formatting code, maybe you'd rather use something like Visitor Pattern.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.