java nio的演进
API的发展绝对是不平凡的。 只有少数几个需要处理的事情。 我们大多数人每天都在使用内部专有API。 现代IDE附带了很棒的工具,可以分解,重命名,上拉,下推,间接,委托,推断,泛化我们的代码伪像。 这些工具使重构我们的内部API变得轻而易举。 但是我们中的一些人在公共API上工作,其中规则发生了巨大变化。 如果正确完成,则对公共API进行版本控制。 每次更改(兼容或不兼容)都应在新的API版本中发布。 多数人会同意,API演化应在主要和次要版本中完成,类似于语义版本控制中指定的内容 。 简而言之:不兼容的API更改发布在主要版本(1.0、2.0、3.0)中,而兼容的API更改/增强发布在次要版本(1.0、1.1、1.2)中。
如果您正在计划,那么您将在很长时间内预见到大多数不兼容的更改,然后才实际发布下一个主要版本。 弃用是Java中提早宣布这样的变化的一个好工具。
接口API的演变
现在,弃用是一个很好的工具,它表明您将要从API中删除类型或成员。 如果要在接口的类型层次结构中添加方法或类型怎么办? 这意味着实现您的接口的所有客户端代码都将中断–至少只要尚未引入Java 8的防御方法即可。 有几种技术可以规避/解决此问题:
1.不在乎
是的,这也是一种选择。 您的API是公开的,但使用的可能不是很多。 让我们面对现实:并不是我们所有人都在JDK / Eclipse / Apache / etc等代码库上工作。 如果您很友好,则至少要等待主要版本引入新方法。 但是,如果确实需要,您可以打破语义版本控制的规则-如果您可以处理引起一群愤怒的用户的后果。
但是请注意,其他平台并不像Java Universe那样向后兼容(通常是根据语言设计或语言复杂性)。 例如,使用Scala将事物声明为隐式的各种方法,您的API并不总是完美的。
2.用Java方式完成
“ Java”方式根本不发展接口。 JDK中的大多数API类型永远都是今天的样子。 当然,这使API感觉很“恐龙化”,并在各种相似类型之间(例如StringBuffer和StringBuilder或Hashtable和HashMap)增加了很多冗余。
请注意,Java的某些部分不遵循“ Java”方式。 最具体地说,JDBC API就是这种情况,它是根据第1节“不关心它”的规则演变的。
3.用Eclipse的方式来做
Eclipse的内部包含大量API。 在Eclipse中/进行开发时, 有很多指南如何开发自己的API(即,插件的公共部分)。 关于Eclipse家伙如何扩展接口的一个示例是IAnnotationHover类型。 根据Javadoc合同,它允许实现还实现IAnnotationHoverExtension和IAnnotationHoverExtension2 。 显然,从长远来看,这种经过改进的API很难维护,测试和记录文档,最终很难使用! (考虑ICompletionProposal及其6(!)扩展类型)
4.等待Java 8
在Java 8中,您将能够使用防御者方法 。 这意味着您可以为新的接口方法提供明智的默认实现 ,如Java 1.8的java.util.Iterator (摘录)所示:
public interface Iterator<E> {// These methods are kept the same:boolean hasNext();E next();// This method is now made 'optional' (finally!)public default void remove() {throw new UnsupportedOperationException('remove');}// This method has been added compatibly in Java 1.8default void forEach(Consumer<? super E> consumer) {Objects.requireNonNull(consumer);while (hasNext())consumer.accept(next());}
}
当然,您并不总是希望提供默认的实现。 通常,您的接口是必须完全由客户端代码实现的合同。
5.提供公共默认实现
在许多情况下,明智的做法是告诉客户端代码,他们可能需要自己承担风险(由于API的演变)来实现接口,而他们应该更好地扩展提供的抽象或默认实现。 一个很好的例子是java.util.List ,可能很难正确实现。 对于简单的而不是对性能至关重要的自定义列表,大多数用户可能选择扩展java.util.AbstractList 。 然后剩下剩下要实现的唯一方法是get(int)和size()。所有其他方法的行为都可以从这两个方法中得出:
class EmptyList<E> extends AbstractList<E> {@Overridepublic E get(int index) {throw new IndexOutOfBoundsException('No elements here');}@Overridepublic int size() {return 0;}
}
遵循的一个很好的约定是,如果您的默认实现为AbstractXXX,则将其命名为默认实现;如果是具体的,则将其命名为DefaultXXX
6.使您的API很难实现
现在,这并不是真正的好技术,而只是一个可能的事实。 如果您的API很难实现(一个接口中有100多个方法),则用户可能不会这样做。 注意: 可能 。 永远不要低估疯狂的用户。 一个示例是jOOQ的 org.jooq.Field类型,它表示数据库字段/列。 实际上,这种类型是jOOQ的内部领域特定语言的一部分 ,提供了可以在数据库列上执行的各种操作和功能。 当然,拥有太多方法是一个例外,并且-如果您不设计DSL-可能表明整体设计不佳。
7.添加编译器和IDE技巧
最后但并非最不重要的一点是,您可以将一些巧妙的技巧应用于您的API,以帮助人们了解他们应该做些什么,以便正确实现基于接口的API。 这是一个艰难的例子,它使API设计人员的意图直接扑向您的脸。 考虑一下org.hamcrest.Matcher API的以下摘录:
public interface Matcher<T> extends SelfDescribing {// This is what a Matcher really does.boolean matches(Object item);void describeMismatch(Object item, Description mismatchDescription);// Now check out this method here:/*** This method simply acts a friendly reminder not to implement * Matcher directly and instead extend BaseMatcher. It's easy to * ignore JavaDoc, but a bit harder to ignore compile errors .** @see Matcher for reasons why.* @see BaseMatcher* @deprecated to make*/@Deprecatedvoid _dont_implement_Matcher___instead_extend_BaseMatcher_();
}
“友好的提醒” ,来吧。
其他方法
我敢肯定,还有许多其他方法可以开发基于接口的API。 我很好奇您的想法!
参考: JAVA,SQL和JOOQ博客上的JCG合作伙伴 Lukas Eder 提供了Java接口的防御性API演变 。
翻译自: https://www.javacodegeeks.com/2013/02/defensive-api-evolution-with-java-interfaces.html
java nio的演进