演进式架构_Java接口的防御性API演进

演进式架构

演进式架构

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感觉很“恐龙化”,并在各种相似类型(例如StringBufferStringBuilderHashtableHashMap)之间增加了很多冗余。

请注意,Java的某些部分不遵循“ Java”方式。 最具体地说,JDBC API就是这种情况,它根据#1节的规则发展:“不必关心”。

3.用Eclipse的方式来做

Eclipse的内部包含大量API。 在Eclipse中/进行开发时, 有很多指南如何开发自己的API(即,插件的公共部分)。 关于Eclipse人员如何扩展接口的一个示例是IAnnotationHover类型。 根据Javadoc合同,它允许实现也实现IAnnotationHoverExtensionIAnnotationHoverExtension2 。 显然,从长远来看,这种经过改进的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.8
    default 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> {
    @Override
    public E get(int index) {
        throw new IndexOutOfBoundsException('No elements here');
    }

    @Override
    public 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
     */
    @Deprecated
    void _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

演进式架构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值