扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
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); } |
《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,后面的留言也提到这个了)。如果一个方法可以被覆盖,一定要在文档中清楚的描述这个方法被覆盖后带来的后果,最好还能提供一些例子程序进行演示以避免开发人员误用。
总结
以下是当前文章一些讨论的意见(因为比较多,所以我没有全部翻译,但是国外技术论坛上的一些讨论,其价值往往比文章的价值更大,建议可以自行阅读一下。):
Gregor Zeitlinger写道:
如果使用作者给出的API标准,C#很多方面是不是做的更好呢。
C#的方法在默认情况是final的,不可被重载。(译注:个人意见,在这一点上C#比Java更好,而且,参数默认就应该是final,因为java参数是传值的,所以也不应该改变)。
C#只有unchecked exception(新的规范中又重新提出了checked exception)。(译注:个人认为checked exception的价值还是很大的,不过在Java中,有些被误用了)。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者