GoF(四人幫)那本《設計模式 可復用面向對象軟件的基礎》可謂是設計模式方面的經典之作,其中介紹的23種設計模式, 也可謂是經典中的經典。但是,設計模式的種類絕不僅僅是這23種,除此之外還有很多巧妙可愛的設計模式值得我們學習。這些 被遺忘的設計模式,也可以堪稱經典之作。今天我們來一起學習被遺忘的設計模式——空對象模式(Null Object Pattern)。 一起看看這個模式會帶給我們怎樣的驚喜?
一、Pattern name
Provide an object as a surrogate for the lack of an object of a given type. The Null Object provides intelligent do nothing behavior, hiding the details from its collaborators.
二、Problem
任何沒有實際應用場景的設計模式,都是在耍流氓。學習設計模式,不僅僅是為了領悟其精髓,更為了在實踐設計當中去運用,去變通,下面我們來看看,什么情況下,這個Null Object Pattern會派上用場呢?
假設這樣一個場景:
在一個圖書信息查詢系統中,你調用一個方法,傳過去你要查找圖書的ID,然后它返回給你,你要查找的圖書對象,這樣你就可以調用對象的方法來輸出圖書的信息。
我想這種場景在程序設計中還是比較常見的。下面,我們來實現以下具體的代碼。
首先,我們來看一下ConcreteBook類的代碼(提供構造函數和展示圖書信息的show()方法。):
public class ConcreteBook { private int ID; private String name; private String author; // 構造函數 public ConcreteBook(int ID, String name, String author) { this.ID = ID; this.name = name; this.author = author; } /** * * Description About show: <br> * 展示圖書的相關信息 * * @version V1.0 */ public void show() { System.out.println(ID + "**" + name + "**" + author); } }
我們再來看看創建圖書對象的圖書工廠的代碼(主要提供一個獲得ConcreteBook的方法):
public class BookFactory { /** * * Description About getBook: <br> * 根據ConcreteBook的ID,獲取圖書對象。 * @param ID 圖書的ID * @return 圖書對象 * @version V1.0 */ public ConcreteBook getBook(int ID) { ConcreteBook book = null; switch (ID) { case 1: book = new ConcreteBook(ID, "設計模式", "GoF"); break; case 2: book = new ConcreteBook(ID, "被遺忘的設計模式", "Null Object Pattern"); break; default: book = null;// 其實這個可以省略,因為初始化已經賦值為null。 break; } return book; } }
最后,來看一下客戶端的代碼:
public class Client { static void main(String[] args) { BookFactory bookFactory = new BookFactory(); ConcreteBook book = bookFactory.getBook(1); book.show(); } }
上面三段代碼很簡單,我就不做詳細解釋了。下面,我們來運行一下,結果如下:
很好,運行很順利,這時,我們把ConcreteBook book = bookFactory.getBook(1);中的1改為2,恩,也運行成功。這時候,我們改成-1。再來運行一下,發現如下報錯:
空指針報錯,是的,這應該是Java初學者見到最多的報錯了。它提示我們第28行book.show()報錯。這是為什么呢?因為我們通過bookFactory.getBook()方法獲取ConcreteBook對象的時候,如果我們傳入的參數,即圖書的ID,屬於非法值(如-1)或者不存在(如3)的話(其實這種情況是經常遇到的。),就會返回null,表示我們查找的圖書信息並不存在。這時,book為null.你再調用book.show()。當然要報空指針的錯誤了。那怎么解決呢?
我們比較常規的做法就是在客戶端加一個判斷,判斷是否為null。如果為null的話,就不再調用show()方法。如果不為null再調用show()方法。更改如下:
public static void main(String[] args) { BookFactory bookFactory = new BookFactory(); ConcreteBook book = bookFactory.getBook(-1); //判斷book對象是否為null。 if (book == null) { System.out.println("book對象為 null。"); } else { book.show(); } }
此時,再運行,就不會報錯了。而是,輸出了:book對象為null。
但是,你有沒有考慮過?這樣做,確實消除了報錯,但是這樣做真的好嗎?你想如果在一段程序中有很多處調用getBook()方法或者有很多個客戶端的話(比如圖書館的查詢終端肯定不止一個啊),豈不是很多處都要判斷book對象是否為null?這還不算壞,如果哪一處沒有判斷,然后報錯了,很有可能導致程序沒法繼續運行甚至崩潰。而且,你要記住,永遠都不要太相信客戶端(Client),不要把整個程序的穩定性寄托在客戶端身上。還有,像上面的處理方法,當獲取對象為null的時候,輸出的提示信息是有客戶端來定制的,這樣豈不是把主動權交給了客戶端,而不是我們系統本身?
那究竟應該如何實現才會更加合適呢?那就要用到我們今天要講的Null Object Pattern——一種被遺忘的設計模式
三、Solution
首先,我們來看一下Null Object Pattern的UML類圖結構:
這個類圖結構其實還是很簡單的,這里面的RealObject其實就相當於我們的ConcreteBook類,而NullObject就是我們將要增加的空對象類,而AbstractObject類就是我們要提出來的父類。我們只是在Client和AbstractObject之間增加了一個BookFactory而已。
下面,我們來改一下我們的代碼:
新增的抽象接口Book類的代碼:
interface Book { // 判斷Book對象是否為空對象(Null Object) public boolean isNull(); // 展示Book對象的信息內容。 public void show(); }
新增的空對象類NullBook類的代碼(繼承Book類):
public class NullBook implements Book { public boolean isNull() { return true; } public void show() { } }
原有的ConcreteBook類修改后的代碼(增加對Book接口的實現,實現isNull方法):
public class ConcreteBook implements Book{ private int ID; private String name; private String author; // 構造函數 public ConcreteBook(int ID, String name, String author) { this.ID = ID; this.name = name; this.author = author; } /** * * Description About show: <br> * 展示圖書的相關信息 * * @version V1.0 */ public void show() { System.out.println(ID + "**" + name + "**" + author); } public boolean isNull(){ return false; } }
工廠類(BookFactory)修改后的代碼(返回對象從ConcreteBook改為Book,並當ID屬於非法值或者不存在時,返回NullBook對象。):
public class BookFactory { /** * Description About getBook: <br> * 根據ConcreteBook的ID,獲取圖書對象。 * @param ID 圖書的ID * @return 圖書對象 * @version V1.0 */ public Book getBook(int ID) { Book book;//將原來的ConcreteBook改為Book switch (ID) { case 1: book = new ConcreteBook(ID, "設計模式", "GoF"); break; case 2: book = new ConcreteBook(ID, "被遺忘的設計模式", "Null Object Pattern"); break; default: book = new NullBook();//創建一個NullBook對象 break; } return book; } }
客戶端的代碼為:
public static void main(String[] args) { BookFactory bookFactory = new BookFactory(); Book book = bookFactory.getBook(-1); book.show(); }
運行一下,我們發現,即使傳入的參數是非法值或者不存在的值時,也不會報錯了,這是Null Object Pattern的第一個好處。但是現在不報錯,也沒有任何輸出,肯定不夠友好,不夠人性化。此時,在NullBook類的show方法中,我們可以定制我們的輸出提醒,當用戶調用空對象的show方法時,就會輸出我們定制的提醒。這回我們可以實現,一處定制,處處輸出,主動權在我們手里,而不是在客戶端的手里。這是Null Object Pattern的第二個好處。
比如我們進行如下修改,修改后的NullBook類代碼:
public class NullBook implements Book { public boolean isNull() { return true; } public void show() { System.out.println("Sorry,未找到符合您輸入的ID的圖書信息,請確認您輸入的不是非法值。"); } }
此時,在執行一下Client,你會發現控制台輸出為:Sorry,未找到符合您輸入的ID的圖書信息,請確認您輸入的不是非法值。
其實,雖然在客戶端我們不進行檢測也可以保證程序不報錯,但是最好的方式,還是進行相應的檢測,如下:
public static void main(String[] args) { BookFactory bookFactory = new BookFactory(); Book book = bookFactory.getBook(-1); if (book.isNull()) { //這里由客戶端定制提醒代碼 System.out.println("兄弟,你輸入的ID不符合規范吧。"); }else{ book.show(); } }
我們看到相比之下,book.isNull()比book == null更加優雅一點。到這里,Null Object Pattern大概就介紹完了。我們可以看到,其實Null Object Pattern還是有點意思的,可以說使整個系統更加堅固了。
四、Consequences
Null Object Pattern,作為一種被遺忘的設計模式,卻有着不能被遺忘的作用。
(1)它可以加強系統的穩固性,能有有效地防止空指針報錯對整個系統的影響,使系統更加穩定。
(2)它能夠實現對空對象情況的定制化的控制,能夠掌握處理空對象的主動權。
(3)它並不依靠Client來保證整個系統的穩定運行。
(4)它通過isNull對==null的替換,顯得更加優雅,更加易懂。
五、總結
到這里,我們的Null Object Pattern就介紹完了,還可以參考這篇資料,也是講得很不錯的。http://www.cs.oberlin.edu/~jwalker/nullObjPattern/
本文轉自:http://blog.csdn.net/qiumengchen12/article/details/44923139