我正在研究SOLID原则,并对与接口有关的依赖关系管理提出疑问。
我正在阅读的书中的一个例子(Gary McLean Hall的自适应代码,通过C#)显示了一个TradeProcessor
类,它将获取交易数据,处理它并存储它在数据库中。交易数据由名为TradeRecord
的类建模。 TradeParser
类将处理将收到的交易数据转换为TradeRecord
个实例。 TradeProcessor
类仅引用ITradeParser
接口,因此它不依赖于TradeParser
实现。
作者使用Parse
方法(在ITradeParser
界面中)返回一个包含已处理交易数据的IEnumerable<TradeRecord>
集合。这是否意味着ITradeParser
现在依赖于TradeRecord
类?
作者不应该做一个像ITradeRecord
接口这样的事情并让Parse
返回ITradeRecord
个实例的集合吗?或者我错过了一些重要的东西?
这是代码(TradeRecord
的实现无关紧要,因此省略了):
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);
}
答案 0 :(得分:43)
这是一个很好的问题,涉及纯度和实用性之间的权衡。
是的,通过纯委托人,你可以说ITradeParser.Parse
应该返回ITraceRecord
接口的集合。毕竟,为什么要将自己与特定的实现联系起来?
但是,你可以进一步。你应该接受IEnumerable<string>
吗?或者你应该有某种ITextContainer
? I32bitNumeric
代替int
?当然,这是 reductio ad absurdum ,但它表明我们总是在某个时刻达到我们正在处理某事这一具体对象的地步(数字,字符串,TraceRecord,等等),而不是抽象。
这也提出了我们首先使用接口的原因,即定义逻辑和功能的契约。 ITradeProcessor
是可以替换或更新的未知实现的合同。 TradeRecord
不是实施合同,是实施。如果它是一个DTO对象,它看起来就像界面和实现之间没有区别,这意味着在定义这个合同时没有真正的目的 - 它隐含在具体课程。
答案 1 :(得分:10)
作者使用Parse方法(在ITradeParser接口中)返回保存已处理交易数据的IEnumerable集合。
这是不是意味着ITradeParser现在依赖于TradeRecord类?
是的,ITradeParser
现在与TradeRecord
紧密结合。鉴于这个问题的学术方法越来越多,我可以看到你来自哪里。但是什么是TradeRecord
?根据定义,记录通常是简单的,非智能的数据(有时称为POCO,DTO或模型)。
在某些时候,抽象的潜在收益比其引起的复杂性更有价值。这种方法在实践中非常普遍 - 模型(我称之为模型)是流经应用程序层的密封类型。作用于模型的图层被抽象为接口,因此可以单独模拟和测试每个图层。
例如,客户端应用程序可能具有View,ViewModel和Repository层。每个层都知道如何使用具体记录类型。但ViewModel可以连接到一个模拟的IRepository,它使用硬编码的模拟数据构建具体类型。此时抽象的IModel没有任何好处 - 它只有直接的数据。