Reputation: 3777
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);
}
Upvotes: 27
Views: 2325
Reputation: 14700
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.
Upvotes: 44
Reputation: 25370
The author has the Parse method (in the ITradeParser interface) return an IEnumerable collection that holds the processed trade data.
Doesn't that mean that ITradeParser is now dependent on the TradeRecord class?
Yes, ITradeParser
is now tightly coupled with TradeRecord
. Given the more academic approach of this question, I can see where you are coming from. But what is TradeRecord
? A record, by definition, is generally a simple, non-intelligent piece of data (sometimes called POCO, DTO, or Model).
At some point, the potential gain of abstraction is less valuable than the complexities it causes. This approach is pretty common in practice - Models (as I refer to them) are sealed types that flow through the layers of an application. Layers that act upon the models are abstracted to interfaces, so that each layer may be mocked and tested separately.
For example, a client application may have a View, ViewModel, and Repository layer. Each layer knows how to work with the concrete record type. But the ViewModel could be wired up to work with a mocked IRepository, which builds up the concrete types with hardcoded, mocked data. There's no benefit to an abstracted IModel at this point - it just has straight data.
Upvotes: 10