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.
posted on 2006-08-30 21:11
Ye Yiliang 閱讀(1412)
評論(4) 編輯 收藏 所屬分類:
Java