【问题标题】:Interfaces (interface/abstract class) are not abstractions?接口(接口/抽象类)不是抽象吗?
【发布时间】:2011-05-22 15:41:43
【问题描述】:

最近,我一直在阅读有关接口是抽象的假设错误概念的帖子。一个这样的帖子是http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstractions.aspx

我有点困惑。如果我没有接口(接口/抽象类),那么我将如何注入我的依赖项并模拟它们?

另外,我听说人们谈论不使用只有一个实现者的接口。喜欢这个博客 - http://simpleprogrammer.com/2010/11/02/back-to-basics-what-is-an-interface/

现在这一切,是不是违反了原则——编程到接口而不是实现?

【问题讨论】:

标签: design-patterns interface abstraction


【解决方案1】:

对接口而不是实现进行编程更多的是使用数据抽象和封装。

当我们在对接口进行编程方面说“接口”时。这种接口意味着类的面向外部的方法和属性。它不必是语言级别的界面。 (关键字界面。)

您应该努力确保您的代码不依赖于其他类的内部细节。

【讨论】:

  • 当我们在对接口进行编程方面说“接口”时。这种接口意味着类的面向外部的方法和属性。它不一定是语言级别的界面。 那么,我一直都弄错了吗?那么,根据你的说法,一个具体的类可以是一个接口吗?
  • 正确。更具体地说,方法和属性的公共签名构成了该类的接口。每当您创建一个类时,您选择公开的任何内容都会成为该类外部接口的一部分。改变它,打破那些依赖它的人。如果另一个类不仅仅依赖于你的接口,(它们依赖于类内部的特定实现细节,例如列表如何排序或数据如何存储),那么即使更改内部的小东西也会破坏它们。
  • 你仍然可以通过继承具体类来做到这一点。但是,这不是重点。如果您不需要将某些东西换成不同的实现,请不要为此构建结构。这样做会产生额外的复杂性,没有当前价值,只有一个充满希望的未来价值。
  • 它的最基本的做法是你点击源代码然后去定义然后找到,哦,它是一个接口,然后你必须检查看看到底是什么实现了那个接口。哦,只有一节课。嗯,是这样吗?我错过了什么吗?哦,没有人只是添加了这个接口以便他们可以进行依赖注入,它并没有真正起到任何作用。
  • 实际上它只是起到了模拟的作用,因为除非你有多个实现,否则依赖注入实际上并没有给你带来任何好处,只是增加了复杂性。
【解决方案2】:

我想说我不同意链接文章中的许多观点:

  • 接口合约。合约有两部分——方法签名(纯语法)和文档

  • 接口抽象。我看不到 LSP 违规的示例。 IRectangle 的例子根本不是一个好例子。 Set extends Collection 也可以这样说,其中不允许添加重复项。如果您通过Collection 您可能会惊讶它不允许重复。使用Collection 接口,通过记录实现者可能会添加限制来解决此问题

  • 泄漏的抽象是不可避免的。但这完全取决于设计师。顺便说一句,“接口是泄漏抽象”意味着它们抽象。

  • 这些家伙似乎错过了“接触”单元测试的机会。模拟实现是使用接口的一个很好的理由(尽管您也可以模拟具体的类)。

  • 我们当前项目的一个很好的例子 - 最初我们只有一个 DAO 实现 - 一个从数据库中获取内容。但后来我们将一些操作切换到了专用的搜索引擎。我们添加了 DAO 的另一个实现,然后就可以了。因此,最初只有一个实现的接口得到了回报。

  • 顺便说一句,最初 SortedSet 在 JDK 中只有一个实现 - TreeSet。现在它有两个。还有更多来自外部库。

  • 1234563

话虽如此,您并不需要所有的接口。但这取决于具体情况。例如,我不使用辅助类的接口。文章的一个有效点是“对接口编程”不一定包含interface 关键字。类的“公共接口”(理论上)是其公共方法的集合。

【讨论】:

  • 模拟实现不是使用接口的好理由,如果您需要模拟实现,它们是必要的邪恶。问题是,我们的编程语言不支持提供模拟实现的想法,所以我们滥用接口来实现结果。虽然,我自己做,但我认为理解它是错误的仍然很重要。我们为额外的接口付出了代价,代价是复杂性和缺乏凝聚力,因为相关的东西在我们的代码中不再直接联系在一起。
  • 正如我所说,也可以在具体类上制作模拟(至少在 Java 中,有一些强大的框架)。但是我不认为在接口上使用模拟是错误的。这是同一概念的不同实现,对吧?它在特定环境(单元测试)中使用的事实并没有从中得到任何东西 - 它仍然是一个具有 2 个有效实现的接口。
  • 另外,你不应该使用辅助类,它们没有明确的职责。他们违反了OO。
  • 他们确实有明确的责任 - 提供帮助;)他们减少了代码重复。
  • 我认为你将很难卖掉那个。
【解决方案3】:

只要不过度,我相信你最好还是创建一个界面。

这是我经常遇到的一个用例,只有一个实现者(在我看来)完全没问题:你有一个 Swing 组件,假设它是一个CarComparisonResultsPanel,它允许用户查看两者之间的比较结果汽车。作为面板的用户,我宁愿拥有一个只有 getCarSimilarities()getCarDifferences()CarComparisonResult 接口,而不是实现这些方法以及其他许多方法的 JPanel 实现。

编辑:为了让我的“不要过度使用”这一点更清楚,这些是一些过度使用的示例:工厂接口、构建器、帮助器/实用程序类、不添加相关公共方法的 GUI 组件给他们的父母,...

【讨论】:

  • 我实际上同意你的看法。在这种情况下,您使用接口是为了使用 CarComparisonResultsPanel 降低代码的复杂性和可见性。
  • 如何决定不要过度使用它?我的意思是你的客户端代码引用接口/抽象类,你总是可以在运行时换出一个实现。另外,如果你没有接口,你将如何模拟它们?这两个原因迫使我认为接口/抽象类在所有情况下都是完全有效的。
  • 我的博客 simpleprogrammer.com 上有一系列回归基础的帖子,深入探讨了接口和这个确切的主题,深入研究了 DI 和 IoC 等,您可能想看看。
  • 正如你所说,“不要过度”是非常模糊的......但我想用文字来表达,没有用真实代码设置场景是不可能的(或者至少超出了我的解释能力! )
【解决方案4】:

接口编程背后的原则不必只限于接口情况。当你设计你的界面时,你要问的一般问题是“我希望它在哪里被消费?由谁消费?目的是什么?”即使在创建实现类时也应该提出的问题。

可能是,在设计接口时,您会意识到您实际上并不需要将其设为接口,并且允许重载和继承就足以进行测试。正如第一篇文章所提到的,如果你总是以 1:1 的关系结束对象和接口,除了“我正在针对接口进行编程”之外没有其他目的,那么你只是把你的代码弄得一团糟。

但是,这并不意味着您不能继续设计该类,就好像您正在为其创建接口/基类一样,使用一组提供基本功能的通用方法/属性/字段,然后添加其他更具体的实现方法/属性/字段。 IMO 这样做仍然符合接口编程的原则。当出现明确而明确的需求时,它肯定也会为您提取接口/基类敞开大门。

【讨论】:

  • 我会说仅仅为了测试目的而允许重载和继承远比创建 1:1 接口差得多。通过继承,您为公共 API 引入了新的复杂性,并为破坏类的行为提供了很多可能性。 1:1的界面至少没有这样的副作用。
猜你喜欢
  • 2017-02-19
  • 1970-01-01
  • 2010-09-20
  • 2023-03-21
  • 2017-11-27
  • 2014-04-12
  • 2010-12-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多