
本文共 1448 字,大约阅读时间需要 4 分钟。
写过java代码的编程人员都有这种感慨——jdk库非常庞大,并且有非常多精心设计的工具可以拿来用。另一方面,jdk版本的向下兼容性也做得非常好,升级版本对旧项目来说没多大困难。由于这两点原因,jdk不可避免地存在一些设计上的缺陷。为了保障旧项目开发团队的利益,这些设计缺陷只能随着众多版本被继承下来。
个人总结了一下,下面几点我们可以把它当作反面教材。
第一宗罪:破坏封装性,公有类直接暴露数据域
如果问一个程序员,为什么javabean不直接将属性设为public,而需要浪费那么多精力搞一堆getter/setter方法。很多人会回答——为了让代码看起来更加oop。
其实这个回答没有抓住重点。我们之所以要实现getter/setter方法,依我看来,至少有两点原因:1、允许我们在调用方法时作限制与记录,例如对setter的参数作边界的判断,在调用方法时增加日志或断点调试;2、可以保持对外开放接口不变的同时修改内部数据结构。
JDK库的几个类破坏了这种封闭性,比较典型的是awt包里的Dimension和Point两个类。从源代码可以看出,这两个类的属性域都直接是public修饰的。
第二宗罪:滥用继承
util包有一个工具类,明显是滥用继承的代表。没错,这个类就是鼎鼎大名的Stack类了。
我们知道Statck数据结构本身的特点就是“后进先出”。那么它理想就应该只有两个方法来操纵数据,一个是push()方法,另一个是pop()方法。但是由于继承了Vector,Stack从父亲那里继承了诸如add(),remove(),set()等违反游戏规则的方法。这简直无法直视!!
第三宗罪:值对象应该设计为不可变对象
不可变对象是指,实例属性经过初始化后在对象的整个生命周期内固定不变。例如jdk的String以及各种基本类型的包装类。
不可变对象易于设计,并且在并发环境下更加安全,无需额外的同步机制。
util包里的Date类属于值对象,类似于人民币一元,十元的概念,应该被设计为不可变对象。也就是说,不应该提供各种setter方法来修改对象的属性域。
第四宗罪:使用常量接口
常量接口是指没有任何方法,只包含表态final域的接口对象,每一个域都导出一个。实现该接口的类即可获得接口的所有常量属性,看起来非常方便!
然而,常量接口模式是对接口的不良使用。首先,常量的使用应属于内部实现细节,实现常量接口会把这样的细节暴露到导出的API中。其次,如果在后续升级中发现一个类不再需要以前的常量,这个类依然必须实现这个常量接口,以确保向下兼容性。
jdk平台io包里的ObjectStreamConstants就属于常量接口,不值得效仿。
那么,如果需要导出常量,最好的选择方案是jdk5引入的枚举类型(enum)。当然,如果某些常量跟某个类或接口联系非常紧密,也可以把这个常量绑定在该类,例如Integer.MAX__VALUE或Math.PI等。
然而,某些开发人员还是喜欢选择常量接口,因为枚举略显繁琐,而类又必须写一串长长的"public static final "修饰。但是他们的使用还是有底线的,当需要某个常量的,代码只会用XXConstants.XX的模式,而不会直接让类实现这个接口。这种使用算是一种折中方案。采用哪种方法,还是看项目规范吧。
考虑到jdk库的庞大以及兼容性,以上列出的几个“瑕疵”也就瑕不掩瑜,无可厚非了。jdk的源码还是非常值得我们好好学习与揣摩的。
发表评论
最新留言
关于作者
