Sam Marion Sam Marion - 21 days ago 4x
C# Question

Working with different objects that inherit interface

I've been working on learning how to use interfaces correctly in c# and I think I mostly understand how they should be used but still feel confused about certain things.

I want to create a program that will create a CSV from Sales Orders or Invoices. Since they are both very similar I figured I could create an IDocument interface that could be used to make a CSV document.

class Invoice : IDocument
public Address billingAddress { get; set; }
public Address shippingAddress { get; set; }
public Customer customer { get; set; }
public List<DocumentLine> lines { get; set; }
// other class specific info for invoice goes here

I can create a method CreateCSV(IDocument) but how would I deal with the few fields that differ from Sales Orders and Invoices? Is this a bad use of interfaces?


You don't inherit interfaces, you implement them; and in this case the interface is an abstraction; it says "all things that implement this interface have the following common characteristics (properties, methods, etc)"

In your case, you have found that in fact Invoices and Sales Orders don't quite share the exact same characteristics. Therefore from the point of view of representing them in CSV format, it's not a great abstraction (although for other things, like calculating the value of the document, it's an excellent one)

There are a number of ways you can work around this though, here are two (of many)

Delegate the work to the classes

You can declare an ICanDoCSVToo interface that returns the document in some kind of structure that represents CSV (let's say a CSVFormat class that wraps a collection of Fields and Values). Then you can implement this on both Invoices and Sales Orders, specifically for those use cases, and when you want to turn either of them into CSV format, you pass them by the IGiveMeCSV interface.

However I personally don't like that as you don't really want your Business Logic mixed up with your export/formatting logic - that's a violation of the SRP. Note you can achieve the same effect with abstract classes but ultimately it's the same concept - you allow someone to tell the class that knows about itself, to do the work.

Delegate the work to specialised objects via a factory

You can also create a Factory class - let's say a CSVFormatterFactory, which given an IDocument object figures out which formatter to return - here is a simple example

public class CSVFormatterLibrary
     public ICSVFormatter GetFormatter(IDocument document)
       //we've added DocType to IDocument to identify the document type.
             return new InvoiceCSVFormatter(document);
        if (document.DocType==DocumentTypes.SalesOrders)
            return new SalesOrderCSVFormatter(document);
        //And so on

In reality, you'd might make this generic and use an IOC library to worry about which concrete implementation you would return, but it's the same concept.

The individual formatters themselves can then cast the IDocument to the correct concrete type, and then do whatever is specifically required to produce a CSV representation of that specialised type.

There are other ways to handle this as well, but the factory option is reasonably simple and should get you up and running whilst you consider the other options.