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); }
Since an interface contains no executable code, you cannot create an instance of an interface. You need to write a class to implement the methods before you can make use of it.
Classes cannot inherit from an interface, since an interface is by definition empty: it only dictates the mandatory implementation of certain members. From the MSDN about interfaces: "An interface contains definitions for a group of related functionalities that a class or a struct can implement."
Yes, you can define an interface inside a class and it is known as a nested interface. You can't access a nested interface directly; you need to access (implement) the nested interface using the inner class or by using the name of the class holding this nested interface.
An interface isn't necessarily a contract of dependencies, it's a contract of functionality. Any implementation can expose its dependencies via constructor(s). But in the event that a different implementation has different (or no) dependencies, it still implements the interface and exposes the functionality.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With