Class Design Help

I come from a long history of using procedural oriented languages and over the past year have been trying to make the transition to a more object oriented approach. I understand most of the concepts and have made great progress but now and then I still run into issues and question myself on my class design. I am currently working on a project which generates a backlog of our financial data. One format in which this data needs to be available to the user in is an excel file.

So my class "Backlog" has a method called GenerateBackLogExcel that returns an excel workbook. My question is, should the GenerateBackLogExcel method actually create the excel workbook as well as populate the workbook before returning it to the user. I know the single responsibility principle has a fine line here because technically you could say that the responsibility is creating an excel formatted version of the backlog to the user, on the other hand the method is creating an excel workbook as well as populating it with data so its technically performing two tasks.

This may not be enough info without seeing the entire class for you to provide feedback, but I would appreciate any feedback on design. Like I said this seems to be my biggest hurdle to overcome on switching how I do things.
nextmedstaffAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Kyle AbrahamsSenior .Net DeveloperCommented:
For me, it all depends on how the classes are going to be used (or more importantly reused).
Will there ever be a time where you need to populate different data, or want to pass data in and get the workbook back?

If so I could see the need for seperating out the two functions:

1)  for getting the data from the backlog
2)  A generic function for populating data using a datatable

Keep in mind though 1 could call 2 and return the workbook.

something like:

public ExcelWB GenerateBackLog()
{
   //get data in datatable
   return   GenerateWB(myDataTable);
}

public ExcelWB GenerateWB(DataTable dt)
{
    // code to generate excel
}

Leaves functionality in case you ever wanted to generate another workbook.  But in this case I would put it in a seperate class.
0
käµfm³d 👽Commented:
The more granular you make the functionality, the more units of work you create, which can then be reused inside of other units of work. If you write one function that both creates a workbook and populates it, then the next time you need to populate a workbook with different data, which part of that initial writing are you now going to duplicate? The creation of the workbook. Both population methods require a new workbook to fill, but if you take your approach, then you will be duplicating code because the logic to create a workbook will be housed in both places. I'm sure you probably did something similar procedurally.

Now, how this comes into play in terms of object-oriented code...  Let's say you were creating a more elaborate component that could create not only Excel workbooks, but also XML files, CSV files, and some other custom, proprietary format. Now you might have a program that creates any of these types of outputs. You (generally) wouldn't want to write a massive if/then block that determines which methods to call in order to create the desired output. Instead, you would abstract away the common parts of your logic, and encapsulate the functionality specific to a particular output format in its own class. You might create an interface to handle the abstraction:

public interface IOutputFormat
{
    string OutputFormatName { get; }

    void CreateFile();
    void PopulateFile();
}

Open in new window


And then implement specific classes that each implement that interface and which will handle the specifics of generating their own types:

ExcelOutputFormatter : IOutputFormat
{
    private string _filename;
    private Excel.Application _excel;
    
    public string OutputFormatName { get { return "Excel"; } }
    
    public void CreateFile()
    {
        this._filename = System.IO.Path.GetTempFileName();
        this._filename = System.IO.Path.ChangeExtension(".xlsx");
        
        this._excel = new Excel.Application();
        this._excel.Open(this_filename);
    }
    
    public void PopulateFile()
    {
        // do something with this._excel object to populate file
    }
}

XmlOutputFormatter : IOutputFormat
{
    private string _filename;
    private object _thingToSerialize;
    
    public XmlOutputFormatter(object toSerialize)
    {
        this._thingToSerialize = toSerialize;
    }
    
    public string OutputFormatName { get { return "XML"; } }
    
    public void CreateFile()
    {
        this._filename = System.IO.Path.GetTempFileName();
        this._filename = System.IO.Path.ChangeExtension(".xml");
    }
    
    public void PopulateFile()
    {
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(object));
        
        using (System.IO.FileStream output = new System.IO.FileStream(this._filename, FileMode.Create))
        {
            serializer.Serialize(output, this._thingToSerialize);
        }
    }
}

CsvOutputFormatter : IOutputFormat
{
    private string _filename;
    private IEnumerable<string> _lines;
    
    public CsvOutputFormatter(IEnumerable<string> toOutput)
    {
        this._lines = toOutput;
    }
    
    public string OutputFormatName { get { return "CSV"; } }
    
    public void CreateFile()
    {
        this._filename = System.IO.Path.GetTempFileName();
        this._filename = System.IO.Path.ChangeExtension(".csv");
    }
    
    public void PopulateFile()
    {
        using (System.IO.StreamWriter writer = new System.IO.StreamWriter(this._filename))
        {
            foreach (string line in this._lines)
            {
                writer.WriteLine(line);
            }
        }
    }
}

Open in new window


And then you can have generic code to handle the actual construction of each output type:

List<IOutputFormat> outputsToCreate = InitializeACollectionOfOutputFormatters();

foreach (IOutputFormat outputFormatter in outputsToCreate)
{
    outputFormatter.CreateFile();
    outputFormatter.PopulateFile();
}

Open in new window


This generic code doesn't have to know anything specific to how files are created or populated--that's the responsibility of each class that implements the IOutputFormat interface. This code knows that since it's dealing with a collection of objects which all implement the IOutputFormat interface it will always have the two methods CreateFile and PopulateFile available to it. This code shouldn't ever have to change. You would only modify the implementation details (i.e. the innards) of each formatter class, and you may also add or remove formatter classes--so long as the new classes implement the IOutputFormat interface.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Programming Theory

From novice to tech pro — start learning today.