Introduce Null Object(引入Null对象)
Introduce Null Object(引入Null對象)
你需要再三檢查某對象是否為null。
將null值替換為null對象。
| ? |
| ? |
動機
多態的最根本好處在于:你不必再向對象詢問"你是什么類型"而后根據得到的答案調用對象的某個行為--你只管調用該行為就是了,其他的一切多態機制會為你安排妥當。當某個字段內容是null 時,多態可扮演另一個較不直觀(亦較不為人所知)的用途。讓我們先聽聽Ron Jeffries的故事。
--Ron Jeffries
我們第一次使用Null Object模式,是因為Rich Garzaniti發現,系統在向對象發送一個消息之前,總要檢查對象是否存在,這樣的檢查出現很多次。我們可能會向一個對象索求它所相關的Person對象,然后再問那個對象是否為null。如果對象的確存在,我們才能調用它的rate()函數以查詢這個人的薪資級別。我們在好些地方都是這樣做的,造成的重復代碼讓我們很煩心。
所以,我們編寫了一個MissingPerson類,讓它返回'0'薪資等級[我們也把空對象(null object)稱為虛擬對象(missing object)]。很快地,MissingPerson就有了很多函數,rate()自然是其中之一。如今我們的系統有超過80個空對象類。
我們常常在顯示信息的時候使用空對象。例如我們想要顯示一個Person對象信息,它大約有20個實例變量。如果這些變量可被設為null,那么打印一個Person對象的工作將非常復雜。所以我們不讓實例變量被設為null,而是插入各式各樣的空對象--它們都知道如何正確地顯示自己。這樣,我們就可以擺脫大量過程化的代碼。
我們對空對象的最聰明運用,就是拿它來表示不存在的Gemstone會話:我們使用Gemstone數據庫來保存成品(程序代碼),但我們更愿意在沒有數據庫的情況下進行開發,每過一周左右再把新代碼放進Gemstone數據庫。然而在代碼的某些地方,我們必須登錄一個Gemstone會話。當沒有Gemstone數據庫時,我們就僅僅安插一個"虛構的Gemstone會話",其接口和真正的Gemstone會話一模一樣,使我們無需判斷數據庫是否存在,就可以進行開發和測試。
空對象的另一個用途是表現出"虛構的箱倉"(missing bin)。所謂"箱倉",這里是指集合,用來保存某些薪資值,并常常需要對各個薪資值進行加和或遍歷。如果某個箱倉不存在,我們就給出一個虛構的箱倉對象,其行為和一個空箱倉一樣。這個虛構箱倉知道自己其實不帶任何數據,總值為0。通過這種做法,我們就不必為上千位員工每人產生數十來個空箱對象了。
使用空對象時有個非常有趣的性質:系統幾乎從來不會因為空對象而被破壞。由于空對象對所有外界請求的響應都和真實對象一樣,所以系統行為總是正常的。但這并非總是好事,有時會造成問題的偵測和查找上的困難,因為從來沒有任何東西被破壞。當然,只要認真檢查一下,你就會發現空對象有時出現在不該出現的地方。
請記住:空對象一定是常量,它們的任何成分都不會發生變化。因此我們可以使用Singleton模式[Gang of Four]來實現它們。例如不管任何時候,只要你索求一個MissingPerson對象,得到的一定是MissingPerson 的唯一實例。
關于Null Object模式,你可以在Woolf [Woolf]中找到更詳細的介紹。
做法
為源類建立一個子類,使其行為就像是源類的null版本。在源類和null子類中都加上isNull()函數,前者的isNull()應該返回false,后者的isNull()應該返回true。
下面這個辦法也可能對你有所幫助:建立一個nullable接口,將isNull()函數放在其中,讓源類實現這個接口。
另外,你也可以創建一個測試接口,專門用來檢查對象是否為null。
編譯。
找出所有"索求源對象卻獲得一個null"的地方。修改這些地方,使它們改而獲得一個空對象。
找出所有"將源對象與null做比較"的地方。修改這些地方,使它們調用isNull()函數。
你可以每次只處理一個源對象及其客戶程序,編譯并測試后,再處理另一個源對象。
你可以在"不該再出現null"的地方放上一些斷言,確保null的確不再出現。這可能對你有所幫助。
編譯,測試。
找出這樣的程序點:如果對象不是null,做A動作,否則做B動作。
對于每一個上述地點,在null類中覆寫A動作,使其行為和B動作相同。
使用上述被覆寫的動作,然后刪除"對象是否等于null"的條件測試。編譯并測試。
范例
一家公用事業公司的系統以Site表示地點(場所)。庭院宅第(house)和集體公寓(apartment)都使用該公司的服務。任何時候每個地點都擁有(或說都對應于)一個顧客,顧客信息以Customer表示:
Customer有很多特性,我們只看其中三項:
本系統又以PaymentHistory表示顧客的付款記錄,它也有其自己的特性:
上面的各種取值函數允許客戶取得各種數據。但有時候一個地點的顧客搬走了,新顧客還沒搬進來,此時這個地點就沒有顧客。由于這種情況有可能發生,所以我們必須保證Customer的所有用戶都能夠處理"Customer對象等于null"的情況。下面是一些示例片段:
這個系統中可能有許多地方使用Site和Customer對象,它們都必須檢查Customer對象是否等于null,而這樣的檢查完全是重復的。看來是使用空對象的時候了。
首先新建一個NullCustomer,并修改Customer,使其支持"對象是否為null"的檢查:
如果你無法修改Customer,可以使用第266頁的做法:建立一個新的測試接口。
如果你喜歡,也可以新建一個接口,昭告大家"這里使用了空對象":
我還喜歡加入一個工廠函數,專門用來創建NullCtomer對象。這樣一來,用戶就不必知道空對象的存在了:
接下來的部分稍微有點麻煩。對于所有"返回null"的地方,我都要將它改為"返回空對象"。此外,我還要把foo==null這樣的檢查替換成foo.isNull()。我發現下列辦法很有用:查找所有提供Customer對象的地方,將它們都加以修改,使它們不能返回null,改而返回一個NullCustomer對象。
另外,我還要修改所有使用Customer對象的地方,讓它們以isNull()函數進行檢查,不再使用==null檢查方式。
毫無疑問,這是本項重構中最需要技巧的部分。對于每一個需要替換的可能等于null的對象,我都必須找到所有檢查它是否等于null的地方,并逐一替換。如果這個對象被傳播到很多地方,追蹤起來就很困難。上述范例中,我必須找出每一個類型為Customer的變量,以及它們被使用的地點。很難將這個過程分成更小的步驟。有時候我發現可能等于null的對象只在某幾處被用到,那么替換工作比較簡單;但是大多數時候我必須做大量替換工作。還好,撤銷這些替換并不困難,因為我可以不太困難地找出對isNull()的調用動作,但這畢竟也是很零亂很惱人的。
這個步驟完成之后,如果編譯和測試都順利通過,我就可以寬心地露出笑容了。接下來的動作比較有趣。到目前為止,使用isNull()函數尚未帶來任何好處。只有把相關行為移到NullCustomer中并去除條件表達式之后,我才能得到切實的利益。我可以逐一將各種行為移過去。首先從"取得顧客名稱"這個函數開始。此時的客戶端代碼大約如下:
首先為NullCustomer加入一個合適的函數,通過這個函數來取得顧客名稱:
接下來我以相同手法處理其他函數,使它們對相應查詢做出合適的響應。此外我還可以對修改函數做適當的處理。于是下面這樣的客戶端程序:
請記住:只有當大多數客戶代碼都要求空對象做出相同響應時,這樣的行為搬移才有意義。注意,我說的是"大多數"而不是"所有"。任何用戶如果需要空對象做出不同響應,他們仍然可以使用isNull()函數來測試。只要大多數客戶端都要求空對象做出相同響應,他們就可以調用默認的null行為,而你也就受益匪淺了。
上述范例略帶差異的某種情況是,某些客戶端使用Customer函數的運算結果:
你常常可以看到這樣的情況:空對象會返回其他空對象。
范例:測試接口
除了定義isNull()之外,你也可以建立一個用以檢查"對象是否為null"的接口。
使用這種辦法,需要新建一個Null接口,其中不定義任何函數:
通常我盡量避免使用instanceof操作符,但在這種情況下,使用它是沒問題的。而且這種做法還有另一個好處:不需要修改Customer。這么一來即使無法修改Customer源碼,我也可以使用空對象。
其他特殊情況
使用本項重構時,你可以有幾種不同的空對象,例如你可以說"沒有顧客"(新建的房子和暫時沒人住的房子)和"不知名顧客"(有人住,但我們不知道是誰)這兩種情況是不同的。果真如此,你可以針對不同的情況建立不同的空對象類。有時候空對象也可以攜帶數據,例如不知名顧客的使用記錄等,于是我們可以在查出顧客姓名之后將賬單寄給他。
本質上來說,這是一個比Null Object模式更大的模式:Special Case模式。所謂特例類(special case),也就是某個類的特殊情況,有著特殊的行為。因此表示"不知名顧客"的UnknownCustomer和表示"沒有顧客"的NoCustomer都是Customer的特例。你經常可以在表示數量的類中看到這樣的"特例類",例如Java浮點數有"正無窮大"、"負無窮大"和"非數量"(NaN)等特例。特例類的價值是:它們可以降低你的"錯誤處理"開銷,例如浮點運算決不會拋出異常。如果你對NaN做浮點運算,結果也會是個NaN。這和"空對象的訪問函數通常返回另一個空對象"是一樣的道理。
轉載于:https://www.cnblogs.com/chenying99/archive/2012/04/21/2462481.html
總結
以上是生活随笔為你收集整理的Introduce Null Object(引入Null对象)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 要求做一个从网页上导入excel
- 下一篇: Linux中报库或者程序找不到的解决方法