Reputation: 690
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?
Upvotes: 1
Views: 59
Reputation: 7505
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)
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 ICanDoCSVToo
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.
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.
if(document.DocType==DocumentTypes.Invoice)
{
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.
Upvotes: 3