科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Java API设计指南3

Java API设计指南3

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

Java API设计指南

作者: 王磊 来源:IT专家网 2008年5月7日

关键字: 指南 设计 API java

  • 评论
  • 分享微博
  • 分享邮件
谨慎的分包:

  Java在控制类和方法的可见性上,所支持的方式实在乏善可陈,除了public,proteced,private以外,就只能通过pakcage来控制。如果一个类或者方法想让外部的包可见,则所有的类和方法都可以访问它了,不能指定外部哪些类可以访问自身。这就意味着如果将API分成若干个包进行发布,则必须对这些包详细设计,避免减少API的公开性。 最简单的方法当然是把所有的API放在一个包中,这样很容易通过package来降低访问性。如果API不超过30个类,这个方案简直是完美。

  但事事往往不尽如人事,经常API非常大,不适合放在一个包中。这时候可能要不得不进行私有分包了(这里的私有与private不一样的,只是一种伪私有),私有只是不在JavaDoc中输出这些类的文档信息。如果查看JDK,会发现许多以sun.*或者com.sun.*打头的包相关的文档信息并没有包含在JDK的JavaDoc中。如果开发人员主要通过JavaDoc来使用API,那可能根本不会注意到这些包的存在,只有查看源代码或者分析API的人才能看到这些API内容。即使发现了这些没有通过文档公开的类,也不建议使用它们,因为不通过文档公开的API,往往也意味着它可能会随着时间的改变进行演化,也有可能在演化的过程中不能保持兼容性。(译注:象C#支持assembly的访问机制,个人就感觉很好,象Osgi支持Bundle,允许定制输出类也是不错误的解决方案,不过前者是语言级,而后者是框架级。)

  还有将包隐藏起来的一个方式就是在包的名称中包含internal。所以Banana的API可能会有公开的包com.example.banana, com.example.banana.peel,也可能还有s com.example.banana.internal 和com.example.banana.internal.peel。

  别忘记所谓的私包同样是可以访问的,更多时候这样的私包只是出于安全的考虑,建议用户不要随便访问,并没有任何语言级的约束。还有一些技术可以解决这个问题,例如NetBeans的API教程中就给出了一种解决方案。在JMX的API中,则使用了另外一种方式。象javax.management.JMX这个类,就只提供了static方法而没有提供public构造函数。这也就意味着你不能实例化这样一个类。(译注:不明白这个例子的意义。)

  下面在设计JMX时的一些技巧

  不变类是一个很好的设计,如果一个类可以设计成不变类,就不要用可变类!如果详细了解这样设计的原因,请参见《Effective Java》的第十三条。如果没有读过这本书,很难设计出好的API。

  另外字段信息应该是private的,只有static和final修饰的字段信息才能变成public,允许外部访问。这一条是一个非常基础的原则,这里提到这个原则,只是因为在早期的API设计时,有些API违反了这个原则,这里不再给出一个例子了。

  避免奇怪的设计。对于Java代码,已经有了许多约定俗成的方法了,如get/set方法,标准的异常类。即使觉得有了更好的方法,也尽量避免使用这些方法。如果使用了一些奇怪的方法名称,这样使用API的用户必须学习新的内容,不能按照原有的习惯来理解代码,会增加学习成本,也会增加误用的可能。

  再举个例子,象java.nio以及java.lang.ProcessBuilder就是一个不好的设计,它不使用getThing()和setThing()方法这种方式,而使用了thing()和thing(T)这两个方法。许多人认为这是一个不错的设计方法,但是这样违反了常用的方法设计原则,强迫用户来学习这种API。(译注:java.nio和java.lang.ProcessBuilder是指JDK6中的包,害得我在JDK1.4中找了半天,参见http://download.java.net/jdk6/doc/api/java/lang/ProcessBuilder.html,这里所谓的thing和Thing也不是真有这个方法和类,而是ProcessBuilder中的command和command(List)等多个方法。)

  不要实现Cloneable, 即使想某一个类支持对象的复制,这个接口也没有太多的价值,如果真想支持复制功能,提供一个复制构造函数或者是一个static方法来复制对象,又或者提供一个static的工厂方法来创建对象,也会更加有效。例如想让Banana这个类拥有clone的能力,可以使用代码如下:

  public Banana(Banana b) { // copy constructor
  this(b.colour, b.length);
  }
  // ...or...
  public static Banana newInstance(Banana b) {
  return new Banana(b.colour, b.length);
  }

构造函数的优点就在于子类可以调用父类的构造函数。static函数则是可以返回具体类的子类实现。

  《Effective Java》书中第十条则给出了clone()带来的痛苦。

  (译注:个人不同意这个观点,我觉得clone非常有用,特别是在多线程的处理中,我会再撰写关于clone方面的文章,而且前面提到的缺点也都是可以通过一些设计上的技巧来改正。)

  异常应该尽可能的是unchecked类型的,《Effective Java》书中第41条则给出了详细的说明。如果当前API只能抛出异常,而且开发人员可以对异常进行处理,如释放资源,就可以使用Checked异常。因此所谓的Checked异常就是API内部与外部开发人员进行问题交互的一种方式。如网络异常,文件异常或者是UI异常等信息。如果是输入参数不合法,或者是一个对象的状态不正确,就应该使用Unchecked异常。

  一个类如果不是抽象类,就应当是final类不可被继承。《Effective Java》第15章给出了足够的理由,同时也建议每个方法在默认情况下都应该是final(目前Java正好相反)(译注:这点我也赞成,觉得方法默认为final更好,但是目前Java发展到当前情况下,已经不可能大规模的更改了,不能不说是Java语言的一个遗憾之处,C#这一点处理的更好,默认为final,后面的留言也提到这个了)。如果一个方法可以被覆盖,一定要在文档中清楚的描述这个方法被覆盖后带来的后果,最好还能提供一些例子程序进行演示以避免开发人员误用。

  总结

  •   设计需要演化,否则会降低它的价值。
  •   先保证API的正确性,在此基础上再追求简单和高效
  •   接口并不如想像中的那么有用。
  •   谨慎分包可以带来更多的价值。
  •   不要忘记阅读《Effective Java》(译注:难道作者和Josh Bloch's有分赃协议不成。)

  以下是当前文章一些讨论的意见(因为比较多,所以我没有全部翻译,但是国外技术论坛上的一些讨论,其价值往往比文章的价值更大,建议可以自行阅读一下。):

  Gregor Zeitlinger写道:

  如果使用作者给出的API标准,C#很多方面是不是做的更好呢。

  C#的方法在默认情况是final的,不可被重载。(译注:个人意见,在这一点上C#比Java更好,而且,参数默认就应该是final,因为java参数是传值的,所以也不应该改变)。

  C#只有unchecked exception(新的规范中又重新提出了checked exception)。(译注:个人认为checked exception的价值还是很大的,不过在Java中,有些被误用了)。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章