Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free.

I've designed the below code for one of the requirement. I have to write different tests for the code and I would need feedback on where I can improve the design.

Requirement:

  1. Read the customer details from csv, calculate his average expenses on different items on yearly and write to csv again.
  2. Read the customer details from csv, calculate his total expenses on faimly and write to csv again.

As both the ExpenseCalculator uses CsvReaderWriter function, is it better to create the abstract class which implements csvReaderWriter functionality and derive from abstract class or is it better to use Interface?

 

    public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Expenses { get; set; }
    // other details
}

public interface ICsvReaderWriter<T>
{
    IEnumerable<T> ParseCsv(string filePath);
    void WriteCsv(string filePath, IEnumerable<Customer> customers);
}

public class ReaderWriter : ICsvReaderWriter<Customer>
{
    public IEnumerable<Customer> ParseCsv(string filePath)
    {
        throw new NotImplementedException();
    }

    public void WriteCsv(string filePath, IEnumerable<Customer> customers )
    {
        throw new NotImplementedException();
    }
}

public interface IExpenseCalculator
{
    void CalculateExpenses(string filePath);
}

public class ItemExpenseCalculator : IExpenseCalculator
{
    private readonly ICsvReaderWriter<Customer> _csvReaderWriter;
    public ItemExpenseCalculator(ICsvReaderWriter<Customer> csvReaderWriter)
    {
        this._csvReaderWriter = csvReaderWriter;
    }

    public void CalculateExpenses(string filePath)
    {
        var customers = _csvReaderWriter.ParseCsv(filePath);
        // do manipulation
        _csvReaderWriter.WriteCsv(filePath, customers);
    }
}

public class FamilyExpenseCalculator: IExpenseCalculator
{
    private readonly ICsvReaderWriter<Customer> _csvReaderWriter;

    public FamilyExpenseCalculator(ICsvReaderWriter<Customer> csvReaderWriter)
    {
        this._csvReaderWriter = csvReaderWriter;
    }

    public void CalculateExpenses(string filePath)
    {
        var customers = _csvReaderWriter.ParseCsv(filePath);
        // do manipulation
        _csvReaderWriter.WriteCsv(filePath, customers);
    }
}

public class Program
{
    private void Main()
    {
        IExpenseCalculator itemExpenseCalculator = new ItemExpenseCalculator(new ReaderWriter());
        itemExpenseCalculator.CalculateExpenses(@"D:\");

        IExpenseCalculator familyExpenseCalculator = new FamilyExpenseCalculator(new ReaderWriter());
        familyExpenseCalculator.CalculateExpenses(@"D:\");
    }
}
share|improve this question

closed as unclear what you're asking by JacquesB, Snowman, durron597, enderland, GlenH7 Aug 14 at 21:12

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

    
does this code work as expected? –  gnat Aug 14 at 10:42
    
I just want the design to be reviewed. Do I still need to post the code? and why it's already -1 –  SabVenkat Aug 14 at 10:43
2  
Isn't there a Code Review StackExchange site? –  Lars Viklund Aug 14 at 10:54
1  
this is the comment when I post the code in code review, "The sites scope is only to review code. We don't review design. For design related question you can check out programmers.se but make sure to read their help center first to check if your question is on topic there". Thanks for your comment. Really frustrating to move across different sites. If needed, I will delete my post –  SabVenkat Aug 14 at 10:56
2  
see Design Review: on-topic or not? "generally, design review type questions are on-topic. However, the problem is how broad they are. My concern for this type of question is that most of them may be more suited to a discussion environment. I do think that there are good design review questions, but they need to be clear and specific and not soliciting general feedback..." –  gnat Aug 14 at 11:18

1 Answer 1

up vote 6 down vote accepted
  1. It's not a very good design, because it doesn't adhere to Single Responsibility Principle .

    • ICsvReaderWriter has two responsibilities, as the name itself indicates. I wouldn't lump them together into one class. Besides, the Reader / Writer distinction is sort of idiomatic in the .NET world (think StreamReader / StreamWriter - you won't find a StreamReaderWriter in the standard library).

    • IExpenseCalculator doesn't only calculate expenses, which is quite easy to tell because its method takes a filePath parameter. The Single Responsibility Principle states that a class should have one, and only one reason to change. Your IExpenseCalculator, however, has several potential reasons to change. It's not just if expenses are to be calculated differently, but also if the output of these calculations has to be retrieved or persisted differently... or not persisted but passed further along etc. And you can't easily unit test your ItemExpenseCalculator (to ensure that it works), because it pulls its input from the file system and pushes the output there as well.

  2. Inheritance hierarchy seems to be upside down to me, too. The interface is ICsvReaderWriter, but the implementation is simply a ReaderWriter. So the base interface is more specific than the child class, because its name already defines the concrete format, whereas the derived class sports a universal name omitting technical details.

    Why would the interface care about the storage format? I'd expect this to be designed the other way round: a generic IWriter interface that could be implemented by a concrete CsvWriter, or an XmlWriter, or a JsonWriter if there is a need.

    See the principle of abstraction for reference. Key takeaway: "the higher the level, the less detail. The lower the level, the more detail."

  3. There's also a slip-up in the interface definition:

     public interface ICsvReaderWriter<T>
     {
         IEnumerable<T> ParseCsv(string filePath);
         void WriteCsv(string filePath, IEnumerable<Customer> customers);
     }
    

    Shouldn't that rather be

    void WriteCsv(string filePath, IEnumerable<T> objects);
    
  4. Naming is a bit off. If the interface is a Reader, not a Parser, why is the method named Parse? It introduces semantic noise. Synonyms are good in creative writing, but in software development we should strive for unambiguity.

  5. And a minor remark... not really a design issue, just a matter of code style.

    private readonly ICsvReaderWriter<Customer> _csvReaderWriter;
    
    public ItemExpenseCalculator(ICsvReaderWriter<Customer> csvReaderWriter)
    {
        this._csvReaderWriter = csvReaderWriter;
    }
    

    Some prefer to prefix fields with an underscore (_csvReaderWriter), some don't like it and then they have to explicitly use this to differentiate between fields and parameters of the same name. To each his own, it's a matter of taste and consistency. However doing both at once makes no sense. It's like wearing belt and braces. If you're referencing the current object explicitly with this, what purpose does the underscore serve?

share|improve this answer
    
How do I change the ItemExpenseCalculator then? Can you give some ideas. –  SabVenkat Aug 15 at 5:47
1  
@SabVenkat I don't know what it does exactly, but one step towards a better design would be to make it accept "pure" data (Customers) and spit out processed data at the end. But leave the file system out of it. The class wouldn't care if they were only just read from a csv file, nor what is going to happen with them afterwards. –  Konrad Morawski Aug 15 at 12:48
    
Thanks Konrad. I will remove the file system out of it and call it from Main. But rather than calling it from Main is there any way I can do it?? –  SabVenkat Aug 15 at 15:31
    
@SabVenkat yes, it's actually preferable if you wrapped it all in another class (not Main). A class taking an IReader, IWriter and an IExpenseCalculator, and putting them together to use. Of course if it's just a one-off, throw-away program, having clean design doesn't matter very much, but since you're asking about it... –  Konrad Morawski Aug 15 at 19:11

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