2006 年 8 月 30 日 星期三
谈优先选择不变类
不变类就是实例不能被改变的类, Effective Java 的 Item13 详细的探讨了为什么设计中要优先考虑将一个类设计为不变的,在细读后感觉平时自己的设计和这个观念有比较大的沟壑,值得总结一下这个 item ,并反思一下如何去实践这个 best practice 。
不变类的优点有如下几个:容易设计,实现和使用,不易出错,安全性更好。
不变类很简单,它只会有一个状态,所以一旦在实例化的时候保证了这个不变实例复合某些业务逻辑上的要求,它就会一直,保持这些要求和规约得到满足。而一个可变类,则相对来说不可靠得多。
不变类是线程安全的,不需要考虑同步的问题,可以安全地共享。同时也不需要在共享过程中的传递引用环节使用“防御性复制”( item24 详谈,主要是保持数据的复制而不是引用的复制)。
由于不变类的上述优点,使得它非常适合成为其他类的组成部分,如果你明确了一个组件是不变类,则能在维护整体的业务逻辑稳定性方面轻松很多。非常显著的一个例子就是用于 map 的 keys 和 set 的 elements 时,不变类的稳定性保证了数据不会不小心被改变。反之,如果是用可变类, key 对象的一次逻辑上不恰当的变动(语法上无错,可以通过编译)可能导致另一处代码在 map 中获取 elements 时找到不正确的值,而实际经验中,我们深刻体会到的一件事情就是不被编译器发现而且不抛出异常的程序错误往往会浪费极大的精力,而且要靠好的视力和丰富的调试经验才能发现。
不变类的唯一缺点就是对应每个不同的值,必须存在一个单独的实例。当需要频繁变更值的时候,性能大为降低,典型的例子就是拿着 String 来反复折腾,而不用 StringBuffer 。而 StringBuffer 正是用来解决这一缺点的,当一个不变类可能需要频繁改变值的时候,就需要设计这么一个对应的可变类来暂时替身。
那么怎么在实际设计中贯彻这种优先选择不变类的思想呢,总的原则就是:除非你找到非常好的理由来设计为一个可变类,否则就应该设计为不变类,要有意的抵抗为每个变量配上 getter 和 setter 的冲动,当然我不是在说你要违反 javabean 的基本要求。。。基本上,小的存放数值的对象都应该设计为不变类,如果该类的值经常改,就配上对应的可变类。而总有些类显然不可能是不变类的,这时也要尽可能的限制它的可变性。类的构造器应该实例化一个充分满足逻辑不变式的实例,而不要交出一个半成品,也不应该提供构造器之外的一个所谓的初始化方法,除非是有极好的理由。同理,也不要提供“重设初始化”方法以达到实例复用的目的,比起它带来的危害,这个垃圾回收环保复用的思路带来的性能提升实在微不足道。
最后,归纳一下设计不变类的 5 条规则:
1. 不提供任何修改对象的方法。
2. 保证没有任何一个方法会被覆盖。
3. 所有的域都是 final 的。
4. 所有的域都是 private 的。
5. 如果不变类内部包括可变的子对象,保证它绝对不会被其他代码获取引用。
要解释的是第 5 ,第 4 点并不能保证这一点,要做到第 5 点,就必须注意在不变类的实例化中,决不通过其他代码提供的引用来赋值给这个子对象,也决不提供这个子对象的引用的获取方法,因为这些传入传出的引用在别的代码中可以被用来修改对象的域。在构造器,读取方法中使用防御性复制,只传值,不传引用。而且是递归性的防御性复制,关于这一话题,在以后的随笔中再详细探讨。
好了,这一篇就到这里,我从中学到的东西,总结起来一句话,就是要非常吝啬类的可变性,从而获取坚固,不易错的类结构。
Wednesday, August 30, 2006
Talk about favoring immutability
Immutable class is the class whose instance can not be modify. Item 13 for Effective Java discussed in detail about why one should favor it rather than a mutable class. I felt that my own designing in practice defers from the idea quite a lot, so it is worthwhile to summary this topic and ponder how to implement this best practice.
Immutable class has several merits: easy to design, implement and use, less prone to error and better safety.
Immutable class is simple, it only have exactly one state, once the invariant has been established at the time it was initialized, these invariant can be guaranteed for all time. It is more reliable than mutable class.
It is thread safe, synchronization consideration can be omitted and it can be shared freely. You don’t have to make defense copy when sharing it. (Item 24 take about defensive copy, main idea is copying values instead of reference of objects.)
These qualities makes immutable class excellent as building block of other class. It is much easier to maintain the invariant of a class when you know that the underlying components are immutable classes. Especially using in keys of a map and elements of a set, once they are put into map or set, their immutability guaranteed the data will stay correct. On the contrary, if in the case of mutable class, a logically incorrect evaluation of a key object (which is correct in syntax, compilable) may cause an getObject() from another place gets a incorrect value, and we deeply aware of that the fact a error that neither discoverable in compile time nor throwing a exception, is extremely time wasting to find out, sometimes it is all about good eyesight and skilled debug techniques.
The only disadvantage of immutable class is that for every distinct value, it has to have a separate object, frequent operations that create new objects will harm the performance. The most familiar example is String is used to do lots of modification, while leaving StringBuffer alone. While StringBuffer is the very solution for the very problem, when a mutable class has to be changing value a lot, a companion modifiable class shall be provided.
Then how we can implement the idea into actual design? The key point is to make it a immutable class unless you find a good reason to make it mutable one. Try to resist the impulse to provide setter along with getter when designing a field, well, I am not asking you to violate the basic rule of javabean…basically, small value objects shall be immutable. Provide companion class when it needs to be change a lot. There are some class that immutable is impossible, then try to limit the mutability of them. Constructor shall initial the object fully with invariants fulfilled, don’t initial partly and then making a “initial method” with it. For the same reason, never provide a so called “reinitial method”, it brings little if any performance benefit at the cost of increased complexity.
For the last thing, these are 5 rules for making a class immutable:
1. Don't provide any methods that modify the object (known as mutators).
2. Ensure that no methods may be overridden.
3. Make all fields final.
4. Make all fields private.
5. Ensure exclusive access to any mutable components.
No.5 shall be explained a little bit, notice that No.4 doesn’t ensure it. To achieve No.5, it have to avoid using reference from other objects when initialing a internal mutable object, and never provide accessor to these objects, because reference passed inward or outward can be used to modify these objects. Use defensive copy in constructors and accessors.
And that’s all of it, what I have learn can be summarized into one sentence: be niggardly when designing the mutability of a class, it brings back rigidity and less error prone structure.