【问题标题】:Can you have an interface be dependent on a class?你可以让一个接口依赖于一个类吗?
【发布时间】:2016-04-28 16:31:25
【问题描述】:

我正在学习 SOLID 原理,并且有一个关于与接口相关的依赖管理的问题。

我正在阅读的书中的一个示例(Gary McLean Hall 的Adaptive Code via 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);
}

【问题讨论】:

  • 另请注意:作者主张将接口及其实现保存在单独的程序集中(即楼梯模式)。如果我遵循该模式,TradeRecord 将需要出现在两个程序集中,否则我必须从实现程序集引用到实现程序集(从而创建循环引用)。
  • 其实 TradeRecord 的实现是相关的。如果那是纯数据类(只包含没有任何逻辑的属性),那么没有太多理由为它引入接口。
  • 我认为这是因为 TradeRecord 只是一个简单的 DTO 对象。当然你可以为这个类创建一个接口,但是你会为你创建的每个其他类创建接口吗?您可以将 TradeRecord 类视为合同。
  • @Evk 作者没有提供实现,只引用了几个属性。
  • 那么我认为我们可以放心地假设作者的意思是它是一个纯数据对象,因此不需要为它创建接口。至于您的第一条评论 - 在这种情况下,它应该存储在“接口”程序集中。

标签: c# dependency-management solid-principles


【解决方案1】:

这是一个很好的问题,涉及纯度和实用性之间的权衡。

是的,通过纯主体,您可以说ITradeParser.Parse 应该返回ITraceRecord 接口的集合。毕竟,为什么要将自己束缚在特定的实现上?

但是,您可以更进一步。你应该接受IEnumerable&lt;string&gt; 吗?或者你应该有某种ITextContainerI32bitNumeric 而不是 int?这当然是reducio ad absurdum,但它表明我们总是在某个时候达到我们正在处理某事的地步,一个具体的对象(数字、字符串、TraceRecord 等),而不是抽象。

这也提出了我们首先使用接口的原因,即为逻辑和功能定义合约。 ITradeProcessor 是可以替换或更新的未知实现的合同。 TradeRecord 不是实施合同,它实施。如果它看起来是一个 DTO 对象,那么接口和实现之间就没有区别,这意味着定义这个契约没有真正的目的 - 它隐含在具体类中。

【讨论】:

  • 很好地解释了,现在这完全有道理了。顺便说一句:在实现 Stariway 模式时,TradeRecord 类是否与接口存在于同一程序集中,以便接口和实现可以引用TradeRecord(正如@Evk 在评论中提到的那样)?跨度>
  • 是的,我认为他们会坐在同一个大会上。这个程序集——通常称为“Common”或“Shared”或“Interfaces”,包含在您的代码中公开和实现的公共共享合同。在不了解 TradeRecord 的情况下,您无法使用或实施 ITradeProcessor。
【解决方案2】:

作者有 Parse 方法(在 ITradeParser 接口中)返回一个 IEnumerable 集合,该集合保存已处理的交易数据。

这是否意味着 ITradeParser 现在依赖于 TradeRecord 类?

是的,ITradeParser 现在与TradeRecord 紧密耦合。鉴于这个问题更具学术性,我可以看到你来自哪里。但是TradeRecord 是什么?根据定义,记录通常是简单的非智能数据(有时称为 POCO、DTO 或模型)。

在某些时候,抽象的潜在收益不如它所导致的复杂性有价值。这种方法在实践中很常见 - 模型(正如我所指的那样)是流经应用程序层的密封类型。作用于模型的层被抽象为接口,以便可以单独模拟和测试每一层。

例如,一个客户端应用程序可能有一个 View、ViewModel 和 Repository 层。每一层都知道如何使用具体的记录类型。但是 ViewModel 可以连接到一个模拟的 IRepository,它使用硬编码的模拟数据构建具体类型。在这一点上,抽象的 IModel 没有任何好处 - 它只有直接的数据。

【讨论】:

  • 感谢您的回答。我知道我可能想多了,事实就是这样。
  • 我不会说这是多虑了。更多的是将理论付诸实践,并在应用层中找到最能从抽象中受益的地方。
猜你喜欢
  • 2023-03-04
  • 1970-01-01
  • 2010-10-13
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-04
相关资源
最近更新 更多