从灵活性的角度来看,界面非常棒。但万一,大量客户端使用接口。在保持旧的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();
}
为避免这种情况,我们必须在所有客户端中明确实施新方法。代码。
所以我认为接口和这个场景如下:
编辑:Default
方法确实是Java接口的一个很好的补充,许多人在他们的答案中已经提到过。但我的问题更多的是在代码设计的背景下。如何在客户端上强制实现方法是接口的固有特性。但是,接口和客户端之间的这种合同似乎很脆弱,因为功能最终会发展。
答案 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
的新界面,并在需要时使用它。