I'm studying SOLID principles and have a question about dependency management in relation to interfaces.
An example from the book I'm reading (Adaptive Code via C# by Gary McLean Hall) shows a TradeProcessor
class that will get the trade data, process it, and store it in the database. The trade data is modeled by a class called TradeRecord
. A TradeParser
class will handle converting the trade data that is received into a TradeRecord
instance(s). The TradeProcessor
class only references an ITradeParser
interface so that it is not dependent on the TradeParser
implementation.
The author has the Parse
method (in the ITradeParser
interface) return an IEnumerable<TradeRecord>
collection that holds the processed trade data. Doesn't that mean that ITradeParser
is now dependent on the TradeRecord
class?
Shouldn't the author have done something like make an ITradeRecord
interface and have Parse
return a collection of ITradeRecord
instances? Or am I missing something important?
Here's the code (the implementation of TradeRecord
is irrelevant so it is omitted):
TradeProcessor.cs
public class TradeProcessor
{
private readonly ITradeParser tradeParser;
public TradeProcessor(ITradeParser tradeParser)
{
this.tradeParser = tradeParser;
}
public void ProcessTrades()
{
IEnumerable<string> tradeData = "Simulated trade data..."
var trades = tradeParser.Parse(tradeData);
// Do something with the parsed data...
}
}
ITradeParser.cs
public interface ITradeParser
{
IEnumerable<TradeRecord> Parse(IEnumerable<string> tradeData);
}
This is a good question that goes into the tradeoff between purity and practicality.
Yes, by pure principal, you can say that ITradeParser.Parse
should return a collection of ITraceRecord
interfaces. After all, why tie yourself to a specific implementation?
However, you can take this further. Should you accept an IEnumerable<string>
? Or should you have some sort of ITextContainer
? I32bitNumeric
instead of int
? This is reductio ad absurdum, of course, but it shows that we always, at some point, reach a point where we're working on something, a concrete object (number, string, TraceRecord, whatever), not an abstraction.
This also brings up the point of why we use interfaces in the first place, which is to define contracts for logic and functionality. An ITradeProcessor
is a contract for an unknown implementation that can be replaced or updated. A TradeRecord
isn't a contract for implementation, it is the implementation. If it's a DTO object, which it seems to be, there would be no difference between the interface and the implementation, which means there's no real purpose in defining this contract - it's implied in the concrete class.