Introduce Null Object (引入Null對象)

Summary: java

你須要再三檢查某對象是否爲null。null值替換爲null對象函數

Motivation:  測試

多態的最根本好處在於:你沒必要再向對象詢問「你是什麼類型」,然後根據獲得的答案調用對象的某個行爲—你只管調用該行爲就是了,其餘的一切多態機制會爲你安排穩當。當某個字段內容是null時,多態可扮演另外一個較不直觀(亦較不爲人所知)的用途。 spa

Mechanics: code

1.爲源類創建一個子類,使其行爲就像是源類的null版本。在源類和null子類中都加上isNull() 函數,前者的isNull() 應該返回false,後者的isNull() 應該返回true。 對象

  • 下面這個辦法也可能對你有所幫助:創建一個nullable接口,將isNull() 函數放在其中,讓源類實現這個接口
  • 另外,你也能夠建立一個測試接口,專門用來檢查對象是否爲null。

2.編譯 接口

3.找出全部「索求源對象卻得到一個null」的地方。修改這些地方,使它們改而得到一個空對象。 ci

4. 找出全部「將源對象與null做比較」的地方。修改這些地方,使它們調用isNull() 函數。 get

  • 你能夠每次只處理一個源對象及其客戶程序,編譯並測試後,再處理另外一個源對象
  • 你能夠在「不應再出現null」的地方放上一些斷言,確保null的確再也不出現。這肯能對你有所幫助

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。這和「空對象的訪問函數一般返回令一個空對象」是同樣的道理

相關文章
相關標籤/搜索