創建和銷毀對象
重點關注對象的創建和銷毀:什么時候、如何創建對象,什么時候、什么條件下應該避免創建對象,如何保證對象在合適的方式下被銷毀,如何在銷毀對象之前操作一些必須的清理行為。
嘗試用靜態工廠方法代替構造器
如果一個
client
要實例化一個對象來使用,傻
b
都知道應該先調用類的構造器來
new
一個對象,之后再調用相應的方法。除了這個方式,
Java Effective
還建議了另一種方法:用靜態工廠方法來提供一個類的實例。以下的例子不反映兩者的優劣,只是反映兩者在代碼實現上的不同,優劣之后再談:
假設咱們要一個顏色為黑色、長度為
50cm
的錘子,自然就用構造器創建一個
Hammer myHammer =? new Hammer(Color.BLACK, 50);
而用靜態工廠方法來實例化一個對象,如下
Hammer myHammer = Hammer.factory(Color.BLACK,50);
也可以用專門的一個工廠類來實例化
Hammer myHammer = Toolkit.factory(“Hammer”, Color.BLACK,50);??
單純從上面的代碼上看,真的只有傻
b
才會選擇靜態工廠的方法,完全就是多此一舉,直接
new
又快又爽,搞這么麻煩做莫斯(武漢話“什么”的意思)?
別急,別急,你急個莫
b
(武漢粗話:基本就是“你急個毛”的意思)?
下面就說說用靜態工廠代替構造器的好處(
advantage
)和不好處(
disadvantage
)。
第一個好處,講你都不信,行家們認為,構造器有一個不好的地方就是:這個方法的簽名(
signture
)太固定了。
構造器的名字是固定的,生個
Hammer
,構造器的名字就是
Hammer
(……),唯一能變化的地方就是參數,假設我的這個錘子有兩個很變態的構造需要:
1
:第一個參數是顏色(
Color
型),第二個參數是錘子頭的重量(
int
型)。
Hammer
(
Color c, int kg
)
{
//remainder omited
}
2
:第一個參數是顏色(
Color
型),第二個參數是錘子的長度(
int
型)。
Hammer
(
Color c, int cm
)
{
//remainder omited
}
感覺滿足需要了,但是細心一看,完了,構造器的參數列表類型重復了,肯定編譯通不過,這是面向對象構造器天生的缺陷——唯一的變化就是參數,參數都分辨不了,就真的分辨不了。
而另外就算參數能分辨的了,構造器一多,它的參數一多,您根本就不知道每個參數是用來干什么的,只能去查閱文檔,在您已經眼花繚亂的時候再去查文檔,一個一個的對,折磨人的活。
這個時候,您就可以考慮用靜態工廠方法來實例化對象了。因為靜態工廠方法有一個最簡單的特點就是:他有可以變化的方法名(構造器的名字變不了)。用名字的不同來代表不同的構造需要,這么簡單的普通的特點在這里就是它相對于構造器的
advantage
。
如上面的錘子的例子可以這樣:
1
:
Hammer.produceByWeight (Color c, int kg){
//remainder omited
}
2
:
Hammer.produceByHeight (Color c, int cm){
//remainder omited
}
這是不是一目了然多了。嗯,我是這樣認為的。
第二個好處,“靜態工廠方法不需要每次都真的去實例化一個對象”——其實這也是另一些優化方法的前提。
構造器的每次
invoke
必定會產生一個新的對象,而靜態工廠方法經過一定的控制,完全可以不用每次
invoke
都生成一個新的對象。
為什么不每次都生成一個對象的原因就不必說了,因為原因太明顯。這個原因就是為什么要“共享”對象的原因。
下面講講通常使用的兩種共享具體策略,也就是具體方法了:
1
:單例模式的需要,一旦需要某個對象有單例的需要,必定對于這類對象的構造只能用靜態工廠方法了。
2
:
flyweight
模式和不變(
immutable
)
模式的需要,這兩個模式很多時候都說一起使用的,一旦一些對象我們認為是不變的,那自然就想拿來重用,也就說共享,而
flyweight
就是用來重用這些小粒度對象的。
如
Boolean.valueOf (boolean)
方法:
Boolean a = Boolean.valueOf (100);
Boolean b = Boolean.valueOf (100);
?a,??b兩個引用都是指向同一個對象。
這些對象都是不變的,而
valueOf
的控制就是用的
flyweight
方法。
這種一個狀態(如上面一個數字)對應的對象只有一個還有一個好處,就是可以直接通過比較“引用”來判斷他們是否
equel
(這里的
equel
是邏輯相等的意思),以前需要
a.equels(b)
,而一旦用“
flyweight
模式和不變(
immutable
)
模式”后,避免了產生多余的相同對象,用
a==b
就可以達到
a.equels(b)
的目的了。這樣當然優化了
performance
。??
第三個好處,其實就是工廠方法的核心好處——我把它稱為“抽象類型構造器”。它可以為我們提供一個抽象類型的實例,同時必要的隱藏了抽象類型的具體結構。這是
new
怎么都達不到的。
這種模式的好處其實就是面向對象的最核心的好處,抽象和具體可以分離,一旦抽象定義好了,具體的東西可以慢慢的變化,慢慢的拓展——開閉原則。
如
Collections Framework API
,都是描述集合類型的接口,也就是對于客戶端來看,只有
Collection
這個類要認識,而實際上,實現這個接口的
Collection
是多種多樣的。如果要讓用戶都知道這些具體實現的
Collection
,就增加了復雜度。
這時,通過一個靜態工廠方法,就可以隱藏各種
Collection
的具體實現,而讓
Client
只使用返回的
Collection
對象就可以了。
這里還可以加上一些權限控制,如這些實現只要對于工廠來講是可以訪問的,不用是
public
的,而他們只要通過
public
的工廠就可以提供給用戶。非常有利于代碼的安全。
靜態工廠方法的第一個缺點就是:使用靜態工廠方法創建的類的構造器經常都是非公共或非
protected
的。
這樣,以后這些類就沒有辦法被繼承了。不過也有人說,不用繼承就用
composition
唄。也是!呵呵。
靜態工廠方法的第二個缺點是:在
jdk
文檔里,這些靜態工廠方法很難跟別的靜態方法相區別。
而文檔中,構造器是很容易看到的。
為了一定程度解決這個問題,我們可以用一些比較特別的名字來給這類靜態工廠方法來命名。最常用的有:
valueOf
——
用來放回跟參數“相同值”的對象。
getInstance
——
返回一個對象的實例。單例模式中,就是返回單例對象。
總結:靜態工廠方法和構造器都有各自的特點。最好在考慮用構造器之前能先考慮一下靜態工廠方法,往往,后者更有用一點。如果權衡了以后也看不出那個好用一些,那就用構造器,畢竟簡單本分多了。