【问题标题】:Why adding a new method to the Java interface breaks the clients that depend on old version?为什么向 Java 接口添加新方法会破坏依赖旧版本的客户端?
【发布时间】:2012-09-15 16:46:54
【问题描述】:

在 Java 中,当您向接口添加新方法时,会破坏所有客户端。当您有一个抽象类时,您可以添加一个新方法并在其中提供一个默认实现。所有客户端都将继续工作。

我想知道为什么界面是这样设计的?

所有旧方法仍然存在,因此似乎不存在向后兼容性问题。 (当然需要有某些例外,但我认为能够在不破坏客户端的情况下向 Java 接口添加新方法可能是一个非常好的主意......)

非常感谢您的 cmets。

【问题讨论】:

  • “休息”是什么意思? JDBC 在版本之间的接口中添加了方法,而不会破坏旧版本。
  • @Peter Lawrey,我的意思是,如果 ClassA 实现 IfsA 并且您将 IfsA 发送给您的客户,那么当您向 IfsA 添加新方法时,具有旧版本 IfsA 的客户端将尝试访问他在旧 IfsA 中使用的方法会出现运行时错误。在此处查看 Erich Gamma 答案(搜索“在向接口添加新方法时使用 Java”)artima.com/lejava/articles/designprinciples.html
  • @Peter Lawrey,实际上你所问的答案就在你的问题之内:)“......版本之间......” - 这不是我的问题中描述的情况。跨度>
  • 您对 API 进行了重大更改,但您不认为这是版本更改?
  • @Peter Lawrey,我不是这么说的。

标签: java design-patterns interface jvm-languages


【解决方案1】:

我可以看到一些可能的中断

  • 您假设客户端将使用新的重载方法,但他们没有这样做,因为代码尚未重新编译。
  • 您添加一个客户端已经拥有的方法,该方法执行不同的操作。
  • 您添加了一个方法,这意味着它们的子类在重新编译时会中断。恕我直言,这是可取的。
  • 您更改了返回类型、方法名称或参数类型,这将在运行时导致错误。
  • 您交换相同类型的参数。这可能是最糟糕和最微妙的错误。 ;)

恕我直言,更容易让您感到悲伤的是细微的问题。但是,我不会假设简单地添加一个方法会破坏代码,我不会假设如果客户端的代码没有出现运行时错误,则意味着他们使用的是最新版本的任何东西。 ;)


如果我编译这段代码

public interface MyInterface {
    void method1();

    // void method2();
}

public class Main implements MyInterface {
    @Override
    public void method1() {
        System.out.println("method1 called");
    }

    public static void main(String... args) {
        new Main().method1();
    }
}

打印出来

method1 called

然后我取消注释 method2() 并仅重新编译接口。这意味着接口有一个 Main 没有实现的方法。然而,当我在不重新编译 Main 的情况下运行它时,我得到了

method1 called

如果我有

public class Main implements MyInterface {    
    public void method1() {
        System.out.println("method1 called");
    }

    public void method2() {
        System.out.println("method2 called");
    }

    public static void main(String... args) {
        new Main().method1();
    }
}

我运行// method2() 注释掉了,我没有问题。

【讨论】:

  • 正确。当您只重新编译接口时,您编译的 Main 类在内部仍将使用旧的接口版本。这不是特定于 java 的,但也发生在例如vb .NET(我假设其他语言)。如果您没有良好的构建顺序(在引用类之前引用类),当您引用类仍然依赖于旧版本时,您将在运行时遇到麻烦。相信我,你需要很长时间才能找到这个问题。 ;)
  • 也试过了。奇迹般有效。好吧,我想这会使我提到的文章(artima.com/lejava/articles/designprinciples.html)和 Erich Gamma 的具体引用产生误导!我们应该将其理解为:“在 Java 中,当您向接口添加新方法时,您不会破坏所有客户端”(不用担心:)
  • 重新编译接口时,运行时只有新版本。如果您进行了不兼容的更改,它将引发错误,但添加方法并不是不兼容的更改。如果我重命名 method1 或更改 ti 签名,即使它的返回类型被忽略,那么就有问题了。
  • 我并不是说你不能破坏客户。例如如果你重载了一个之前没有重载的方法,你的调用代码将不会使用新方法。您必须注意这一点,不要依赖客户端重新编译。如果更改返回类型,即使它被忽略,也会破坏子类。
  • 点赞并接受。但是,我现在担心我错过了 Erich G 上述声明的重点......感觉有点困惑:)
【解决方案2】:

接口就像一个类的模板。当您有一个对象的类实现了某个接口并且您对该接口进行强制转换时,您只能通过该接口访问该对象(及其方法)。因此,您的客户端将始终看到接口提供的所有方法,而不仅仅是那些实际上由类实现的方法。

您的建议会让您想知道您在任何时候处理的对象是否确实具有他们试图调用的方法的实现。

当然,在您的场景中,旧客户端不会发生这种情况,除非您想在一段时间内更新它们并且您依赖于您的对象对您的 IDE 预览的所有方法都有实现。 :)

抽象类的事实是(正如您所提到的)您提供了一个默认实现,因此可以在客户端依赖您的对象实现了这些方法。

希望这有助于解决问题。

问候

【讨论】:

    【解决方案3】:

    Interface 表示一组 ALL 可从该接口对其用户使用的方法,而不是一些可以添加其他方法的方法。这是一份不少也不多的契约。

    这种设计的最大优点是没有歧义。 Java 是一种静态类型语言,需要完全了解接口声明中所有可用的方法。在 JVM 内部,接口类只有一种表示,它需要包含一整套可用的抽象方法。

    当你有一个带有一些实现方法的抽象类时,它的语义是一个无法实例化但其唯一目的是扩展并实现其抽象方法的类。 抽象类强制执行 IS-A 关系,而接口强制执行 BEHAVES-AS

    【讨论】:

      【解决方案4】:

      接口的作用不仅仅是方法的声明。它们构成了基于合同/首次开发的基础。这些合约 API 定义了什么是输入,什么是输出。和任何合同一样,它们只有在满足所有条件时才有效。这就是为什么一个类必须实现已实现接口的所有api的原因。

      有些设计模式定义了处理版本和新开发的方式。在实例化这些接口的实现时应使用的工厂/抽象工厂模式(以便它们可以在内部验证要使用的正确版本是实现)。

      您所要求的灵活性可以通过适当的设计来实现,而不是期望通过编程语言来实现。对于将新版本实现插入旧接口的编程工具是编程错误。但它不同于向后兼容中断。 更新版本的组件的可用性并不一定意味着会有向后兼容性中断。成熟的产品或组件总是支持旧版本,只要它们不会成为维护的真正痛苦。因此,在大多数情况下,除非有一些您想使用的新功能,否则用户不必担心更新版本的可用性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多