接口只有一个实现

时间:2011-07-30 15:33:47

标签: java oop interface

使用jmock时,如果设置类imposteriser,则可以模拟具体类。我注意到类imposterizer在遗留包中,所以我真的不想使用它(特别是因为在我的IDE中使用重构工具提取界面非常容易)。我不喜欢具体类的实例变量。

然而,

提取界面,我注意到我的代码库中出现了一种模式。很多时候接口只有一个实现具体类。我更喜欢尽可能使用接口,但拥有所有这些额外文件似乎非常冗长。此外,每次我想在课程中添加新方法时,更新界面并更新实施者都会有点繁琐。

这只是你为正确抽象付出的代价,还是有一种我没有想过的更好的方法?即使接口中唯一的东西是getters / setters,所有类都应该实现接口吗?

9 个答案:

答案 0 :(得分:10)

如果界面只是getter和setter,这听起来更像是数据而不是行为 - 并且听起来不像我那样的东西嘲笑。我很乐意在其他类的测试中直接使用简单的生产代码。我只为提供服务的类注入依赖项。

我感觉到你的痛苦,但我个人仍然编写界面,即使目前只有一个生产实现。我常常会在嘲笑一段时间后发现我写了一个存根或伪造的实现。伪装可以最终变得更加简单(导致更清晰的测试),除非您真的对测试调用者和服务之间的交互感兴趣。

这也意味着当有人想要查看特定依赖项提供的方法时,他们可以看到只是接口,而不涉及任何实现。

答案 1 :(得分:5)

我要点的一些一般观点:

  • 测试中的模拟是替代实现。因此,即使您的产品代码中只有一个实现,代码库中也会有多个实现。
  • 首选构造函数注入getter和setter。这也将使不可变对象更自然。
  • 接口描述了一段行为的合同,并且这样做的方式是接口的用户不需要知道实现的细节。
  • 不要为那些与这些事情无关的事物建立接口:抽象出实施细节;多种替代实施; mockability;行为。
  • 优先选择具有许多小型组件的设计,而不是大型互连部件的设计。
  • 如果某些内容难以测试或模拟,请尝试将其拆分为具有更简单API的较小部分,然后组合它们。
  • 提供精确的名称,并注意不仅在代码中复制,还在结构和名称中复制。
  • 避免实现继承 - 更喜欢组合。接口继承更难以滥用,并且在需要时经常会自然丢失。

这些要点有望帮助您制作更简单,更易测试和可维护的设计。至少在尝试跟随它们之后有一些经验。

答案 2 :(得分:3)

如果它的冗长和繁琐是不好的风格,无论理论上的优势是什么。

如果使用您的工具轻松提取界面,为什么现在就这样做?当它实际上有一些真正的目的时,为什么不这样做呢。我看到太多的代码构建了未来,而不是解决手头的问题。

当然,关于这些问题的所有争论都只是意见。没有事实可以找到。

答案 3 :(得分:3)

你提到“适当的抽象”。嗯,如果界面对多个类有意义,那是唯一合适的。特别是考虑到你从具体类中提取接口,你可能最终得到了接口中不应该存在的方法。当然,如果首先存在界面本身是有意义的。

我要指出的最后一件事是你的界面似乎并不特别稳定。您似乎暗示向接口添加方法对您来说很常见。当类的合同(即它的接口)如此易变时,我会质疑类的抽象。

总而言之,我不相信立即为所有提供接口构成了适当的抽象。相反,等待类的公共接口首先稳定,然后考虑适当的抽象(即只是点击IDE中的"extract interface..."按钮)从设计角度来看要好得多。另外,类可能会抽象为几个不相关的接口,而不是单个接口。

答案 4 :(得分:1)

如果您的界面主要是属性,那么所有实现类可能都是相关的,也许抽象类更适合您的需求。然后你可以避免一些样板。抽象类的主要功能仍然可以通过接口进一步抽象。

interface IRunner {
      void run();
      int doOtherThing();
}

abstract class Thing implements IRunner {

    //this is class-specific
    abstract void run();
    //this is common to all 'Things'
    int doOtherThing() { return 0; }

    //as are these properties
    public int getProp() {...}
    public void setProp(int val) {...}

}

public class Goo extends Thing {
      public void run() {
         int i = getProp() + doOtherThing();
         makeMagicGoo(i);
      }

}

答案 5 :(得分:0)

如果让额外的接口冒犯你,你可以创建模拟对象(手工)作为你正在模拟的类的子类。

如果一个班级仅仅由(简单的)吸气者和制定者组成,那么嘲笑它几乎没有任何意义。只需使用真实的东西。

答案 6 :(得分:0)

有一个关键的用例,即使在你只编写一个实现的情况下,为对象生成接口也是至关重要的:你希望能够为类的实例生成JDK代理(这很有用)如果你正在做Spring AOP,例如)。可以使用像cglib这样的东西为任意事物构建代理,但它要复杂得多;相比之下,JDK代理很容易。

我也有点担心你的IDE没有尽可能地帮助你。我发现使用我的(Eclipse)我可以在接口中创建方法,如果我只是在实现中用@Override标记它们并选择正确的自动修复选项。 (这并不是说它会选择正确的方法解释,但那是你的工作。)

答案 7 :(得分:0)

请参阅此处发布的答案,该答案将重点放在界面https://softwareengineering.stackexchange.com/a/179265/74889

的合同定义角色上

答案 8 :(得分:0)

我建议这样做:

  • 没有简单的Value-style或DTO-style类的接口,尤其是不可变的类
  • 对于包本构的类,其构造函数中的其他非接口类的依赖性非常有限,因此不需要接口。根据定义,类的非私有方法是它的接口。
  • 根据接口隔离原则
  • ,如果可以提取子接口,请使用接口
  • 我仍然使用包和模块之间的接口,即我更喜欢我的包只暴露公共接口,而不是公共类
  • 尽管Mockito和类似的库具有强大功能,但实际类的模拟仍将调用其构造函数。因此,如果构造函数使用其他实际类或具有一些非平凡的异常抛出逻辑,我将使用接口来避免将单元测试写入其他类时的意外

关于这个主题的几个有用的链接:
https://martinfowler.com/bliki/InterfaceImplementationPair.html
https://rrees.me/2009/01/31/programming-to-interfaces-anti-pattern/