Summary: java
你須要再三檢查某對象是否爲null。將null值替換爲null對象。 函數
Motivation: 測試
多態的最根本好處在於:你沒必要再向對象詢問「你是什麼類型」,然後根據獲得的答案調用對象的某個行爲—你只管調用該行爲就是了,其餘的一切多態機制會爲你安排穩當。當某個字段內容是null時,多態可扮演另外一個較不直觀(亦較不爲人所知)的用途。 spa
Mechanics: code
1.爲源類創建一個子類,使其行爲就像是源類的null版本。在源類和null子類中都加上isNull() 函數,前者的isNull() 應該返回false,後者的isNull() 應該返回true。 對象
2.編譯 接口
3.找出全部「索求源對象卻得到一個null」的地方。修改這些地方,使它們改而得到一個空對象。 ci
4. 找出全部「將源對象與null做比較」的地方。修改這些地方,使它們調用isNull() 函數。 get
5.編譯,測試 源碼
6.找出這樣的程序點:若是對象不是null,作A動做,不然作B動做
7.對於每個上述地點在null類中覆寫A動做,使其行爲和B動做相同。
8.使用上述被覆寫的動做,而後刪除「對象是否等於null」的條件測試。編譯並測試。
範例
一家公用事業公司的系統以site表示地點,庭院宅第(house)和集體公寓(apartment)都是用該公司的服務。任什麼時候候每一個地點都擁有一個顧客,顧客信息以customer表示:
class Site... Customer getCustomer(){ return _customer; } Customer _customer;
Customer 有不少特性,咱們只看其中三項:
class Customer... public String getName(){...} public BillingPlan getPlan(){...} public PaymentHistory getHistory(){...}
本系統又以PaymentHistory表示顧客的付款記錄,它也有其本身的特性:
public class PaymentHistory... int getWeekDelinquentInLastYear()
上面各類取值函數容許客戶取得各類數據。但有時候一個地點的顧客伴奏了,新顧客尚未搬進來,此時這個地點就沒有顧客。因爲這種狀況可能發生,因此咱們必須保證Customer的全部用戶都可以處理「Customer對象等於null」的狀況。下面是一些實例片斷:
Customer customer = site.getCustomer(); BillingPlan plan; if(customer==null) plan=BillingPlan.basic(); else plan = customer.getPlan(); ... String customerName; if(customer == null) customerName = 「occupant」; else customerName = customer.getName(); ... int weeksDelinquent; if(customer == null) weeksDelinquent = 0; else weeksDelinquent = customer.getHistory().getWeeksDelinquentInLastYear();這個系統中有可能有許多地方使用Site和Customer對象,它們都必須檢查Customer對象是否爲null,而這樣的檢查徹底是重複的。看來是使用空對象的時候了。
首先新建一個NullCustomer,並修改Customer,使其支持「對象是否爲null」的檢查:
class NullCustomer extends Customer{ public boolean isNull(){ return true; } }
class Customer... public boolean isNull(){ return false; } protected Customer(){}//needed by the NullCustomer若是你沒法修改Customer,能夠創建一個新的測試接口。
若是你喜歡,也能夠新建一個接口,昭告你們「這裏使用了空對象」
interface Nullable{ boolean isNull(); } class Customer implements Nullable
還能夠加入一個工廠函數,專門用來建立NullCustomer對象。這樣一來,用戶就沒必要知道空對象的存在了:
class Customer ... static Customer newNull(){ return new NullCustomer(); }
接下來的部分稍微有點麻煩。對於全部「返回null」的地方,都要將它改成「返回空對象」。此外,還要把foo==null這樣的檢查替換成foo.isNull()。下列辦法頗有用:查找全部提供Customer對象的地方,將他們都加以修改,使它們不能返回null,改而返回一個NullCustomer對象。
class Site... Customer getCustomer(){ return (_customer==null)?Customer.newNull():_customer; }
另外還要修改全部使用Customer對象的地方,讓它們以isNull()函數進行檢查,再也不使用==null檢查方式。
Customer customer = site.getCustomer(); BillingPlan plan; if(customer.isNull()) plan = BillingPlan.basic(); else plan = customer.getPlan(); ... String customerName; if(customer.isNull()) customerName = "occupant"; elsed customerName = customer.getName(); ... int weeksDelinquent; if(customer.isNull()) weeksDelinquent = 0; else weeksDelinquent = customer.getHistory().getWeeksDelinquentInLasterYear();
毫無疑問,這是本項重構中最須要技巧的部分。對於每個須要替換的可能等於null的對象,都必須找到全部檢查它等於null的地方,並逐一替換。若是這個對象被傳播到不少地方,追蹤起來就很困難。上述範例中,咱們必須找出每個類型爲Customer的變量,以及它們被使用的地點。很難講這個過程分紅更小的步驟。
這個步驟完成,若是編譯和測試都順利經過,咱們將進行下一步動做。到目前爲止,使用isNull()函數還沒有帶來任何好處。只有把相關行爲移到NullCustomer中並去除條件表達式以後,才能獲得實際的好處。咱們能夠逐一將各類行爲移過去。首先從「取得顧客名稱」這個函數開始。
首先爲NullCustomer加入一個合適的函數,經過這個函數來取得顧客名稱:
class NullCustomer... public String getName(){ return "occupant"; }
如今能夠去掉條件代碼了:
String customerName = customer.getName();
接下來咱們以相同手法處理其餘函數,使它們對相應查詢租出合適的響應。
請注意:只有當大多數客戶代碼都要求空對象做出相同相應時,這樣的行爲搬移纔有意義。
範例2:測試接口
除了定義isNull() 以外,也能夠創建一個用以檢查「對象是否爲null」的接口。使用這種辦法,須要新建一個Null接口,其中不定義任何函數:
interface Null {}
而後,讓空對象實現Null接口:
class NullCustomer extends Customer implements Null...而後就能夠用instanceof操做符檢查對象是否爲null:
一般咱們應當儘可能避免使用instanceof操做符,但在這種狀況下,使用它是沒有問題的。並且這種作法還有另外一個好處:不須要修改Customer。這麼一來即便沒法修改Customer源碼,也可使用空對象
其餘特殊狀況
使用本項重構時,你能夠有幾種不一樣的空間對象,例如你能夠說「沒有顧客」和「不知名顧客」,這兩種狀況是不一樣的。果然如此,你能夠針對不一樣的狀況創建不一樣的空對象類。有時候空對象也能夠攜帶數據,例如不知名顧客的使用記錄等,因而咱們能夠在查出顧客姓名以後將帳單寄給他。
本質上來講,這是一個比Null Object模式更大的模式:Special case模式。所謂特例類,也就是某個類的特殊狀況,有着特殊行爲。所以表示「不知名顧客」的UnknownCustomer和表示「沒有顧客」的NoCustomer都是Customer的特例。咱們常常能夠在表示數量的類中看到這樣的「特例類」,例如Java浮點數有「正無窮大」、「負無窮大」和「非數量」(NaN)等特例。特例類的價值是:它們能夠下降你的「錯誤處理」開銷,例如浮點運算決不會拋出異常。若是你對NaN的浮點運算,結果也會是個NaN。這和「空對象的訪問函數一般返回令一個空對象」是同樣的道理