接口可以随时间演变吗?

时间:2015-12-26 09:03:23

标签: java interface

从灵活性的角度来看,界面非常棒。但万一,大量客户端使用接口。在保持旧的mehtod完整性的同时向界面添加新方法将破坏所有客户端'代码作为新方法不会出现在客户端。如下图所示:

public interface CustomInterface {

    public void method1();

}

public class CustomImplementation implements CustomInterface {

    @Override
    public void method1() {
        System.out.println("This is method1");
    }
}

如果在某个时间点之后,我们会在此界面中添加另一种方法,即所有客户端'代码会中断。

public interface CustomInterface {

    public void method1();

    public void method2();

}

为避免这种情况,我们必须在所有客户端中明确实施新方法。代码。

所以我认为接口和这个场景如下:

  1. 曾经写过的界面就像石头雕刻一样。他们很少被认为,并且有望改变。如果他们这样做,他们会花费巨大的成本(重写整个代码),程序员应该为此做好准备。
  2. 继续上述观点,是否可以编写经得起时间考验的接口?
  3. 如何在未来期望其他功能的界面中处理此类场景?这预示着所有客户都被绑定的合同变更。
  4. 编辑:Default方法确实是Java接口的一个很好的补充,许多人在他们的答案中已经提到过。但我的问题更多的是在代码设计的背景下。如何在客户端上强制实现方法是接口的固有特性。但是,接口和客户端之间的这种合同似乎很脆弱,因为功能最终会发展。

6 个答案:

答案 0 :(得分:21)

Java 8中以接口中的默认方法的形式引入了该问题的一种解决方案。它允许在不破坏现有代码的情况下向现有Java SE接口添加新方法,因为它为所有新方法提供了默认实现。

例如,广泛使用的Iterable接口(它是Collection接口的超级接口)添加了两个新的默认方法 - default void forEach(Consumer<? super T> action)和{{ 1}}。

答案 1 :(得分:18)

public interface CustomInterface {
    public void method1();
}

public interface CustomInterface2 extends CustomInterface {
    public void meathod2();
}

除了默认方法之外,您可以使用继承属性,如上所示,新接口将包含所有先前方法以及新方法,并在您所需的情况下使用此接口。

答案 2 :(得分:8)

Java 8引入了方法的默认实现。这些实现驻留在接口中。如果在已经由许多类实现的接口中创建具有默认实现的新方法,则不需要修改所有类,而只需要修改我们希望为新定义的方法具有不同实现的类。默认的一个。

现在,旧的Java版本呢?在这里,我们可以有另一个扩展第一个的接口。之后,我们想要实现新声明的方法的类将被更改为实现新接口。如下图所示。

public interface IFirst {
    void method1();
}

public class ClassOne implements IFirst() {
    public void method1();
}

public class ClassTwo implements IFirst() {
    public void method1();
}

现在,我们希望声明method2(),但它应该只由ClassOne实现。

public interface ISecond extends iFirst {
    void method2();
}

public class ClassOne implements ISecond() {
    public void method1();
    public void method2();
}

public class ClassTwo implements IFirst() {
    public void method1();
}

这种方法在大多数情况下都可以,但它也有缺点。例如,对于ClassTwo,我们需要method3()(并且只有那个)。我们需要一个新的界面IThird。如果以后我们想要添加应由ClassOne和ClassTwo实现的method4(),我们需要修改(但不ClassThree,而implemented IFirst}我们需要更改ISecond { {1}}和IThird

编程时很少有“魔术子弹”。在接口的情况下,最好不要更改。在现实生活中并非总是如此。这就是为什么建议接口只提供“合同”(必须具有的功能),并在可能的情况下使用抽象类。

答案 3 :(得分:5)

未来的界面更改不应该破坏任何有效的界面 - 如果有,它就是一个不同的界面。 (它可能会弃用某些东西,并且在弃用之后的完整周期,可以接受的是,抛出未实现的异常是可以接受的。)

要向界面添加内容,最简洁的答案是从中派生新界面。这将允许使用实现新行为的对象和期望旧的行为的代码,同时让用户适当地声明和/或类型转换以获得对新功能的访问。它有点烦人,因为它可能需要测试实例,但它是最强大的方法,而且它是你在许多行业标准中看到的方法。

答案 4 :(得分:4)

接口是开发人员和客户之间的合约,所以你是对的 - 他们是刻在石头上的,不应该改变。因此,接口应该只暴露(=需求)类中绝对需要的基本功能。

List界面为例。 Java中有许多列表实现,其中许多都是随着时间的推移而发展的(更好的底层算法,改进的内存存储),但是列表的基本“概念” - 添加项目,搜索项目,删除item - 不应该也不会改变。

因此,对于您的问题:您可以使用抽象类,而不是编写类实现的接口。接口基本上是纯抽象类,因为它们不提供任何内置功能。但是,一个可以将新的非抽象方法添加到客户端不需要实现的抽象类中(覆盖)。

以抽象类(= interface)为例:

abstract class BaseQueue {
    abstract public Object pop();
    abstract public void push(Object o);
    abstract public int length();
    public void clearEven() {};
}

public class MyQueue extends BaseQueue {
    @Override
    public Object pop() { ... }

    ...
}

就像在接口中一样,扩展BaseQueue的每个类都受合约约束以实现抽象方法。但是,clearEven()方法不是一个抽象方法(并且已经有一个空实现),因此客户端不会被迫实现它,甚至不能使用它。

这意味着您可以利用Java中抽象类的强大功能来创建非合同绑定方法。您可以根据需要将其他方法添加到基类中,前提是它们不是抽象方法。

答案 5 :(得分:2)

我认为你的问题更多的是关于设计和技术,所以java8的答案有点误导。这个问题早在java8之前就已为人所知了,所以还有一些其他的解决方案。

首先,没有绝对无法解决问题的方法。界面演变带来的不便之处取决于库的使用方式以及设计的深度。

1)如果您设计了界面并忘记在其中加入强制性方法,那么任何技术都无济于事。更好地规划您的设计,并尝试预测客户将如何使用您的界面 示例:想象Machine接口具有turnOn()方法但未使用turnOff()方法。在java8中引入一个带有默认空实现的新方法将防止编译错误,但实际上并没有帮助,因为调用方法将无效。提供工作实现有时是不可能的,因为接口没有字段和状态。

2)不同的实现通常有共同点。不要害怕在父类中保持共同的逻辑。从此父类继承您的库类。这将强制库客户端也从父类继承自己的实现。现在,您可以对界面进行少量更改,而不会破坏所有内容 示例:您决定在您的界面中包含isTurnedOn()方法。使用基本类,您可以编写一个默认的方法实现。不是从父类继承的类仍然需要提供自己的方法实现,但由于方法不是必需的,因此对它们来说很容易。

3)通常通过扩展接口来实现升级功能。没有理由强迫库客户端实现一堆新方法,因为他们可能不需要它们 示例:您决定将stayIdle()方法添加到您的界面。它使您的库中的类更新,但不适用于自定义客户端类。由于此功能是新功能,因此最好创建一个扩展Machine的新界面,并在需要时使用它。