被遺忘的設計模式——空對象模式(Null Object Pattern)


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM