湯致軒 姚圣揚
摘? 要:在Java程序設(shè)計中,經(jīng)常會遇到j(luò)ava.lang.NullPointerException空指針異常,在客戶端總是需要判斷某一對象是否為空對象,這使得客戶端掌握了主動權(quán),且程序不夠友好。文章從這些常見的問題出發(fā),分析了一個在經(jīng)典的23種軟件設(shè)計模式以外的模式——空對象模式,通過圖書管理的場景引出問題,并合理地運用空對象模式對問題進行分析和解決,討論了空對象模式的實現(xiàn)方式及應(yīng)用場景,闡述了對空對象模式的優(yōu)缺點分析與對軟件設(shè)計模式的深入理解。
關(guān)鍵詞:空對象模式;設(shè)計模式;特殊對象;軟件工程;Java
中圖分類號:TP311.52? ? ? 文獻標志碼:A? ? ? ? ?文章編號:2095-2945(2020)30-0001-05
Abstract: In Java programming, we often encounter java.lang.NullPointerException, and in the client end, there is always need to judge whether an object is a null object, which makes the client master grasp the initiative, and the program is not friendly enough. From these common problems, this paper analyzes a pattern other than the classic 23 software design patterns-null object pattern. Through the scene of book management, some problems are pointed out. Therefore, this paper reasonably uses the null object pattern to analyze and solve the problems, discusses the realization and application scenarios of the null object pattern, and expounds the advantages and disadvantages of the null object pattern and the deep understanding of the software design pattern.
Keywords: null object pattern; design patterns; special objects; software engineering; Java
軟件設(shè)計模式是一種為多數(shù)人知道、能被反復(fù)使用、并分類編目的代碼設(shè)計經(jīng)驗的概括與總結(jié)。GoF所著《Design Patterns: Elements of Reusable Object-Oriented Software》是設(shè)計模式方面的經(jīng)典之作,其中介紹了23種設(shè)計模式,但是設(shè)計模式并不止這23種,還有很多實用的軟件設(shè)計模式。本文從這23種模式以外的一種設(shè)計模式——空對象模式出發(fā),通過應(yīng)用實例對空對象模式進行分析,深入挖掘此模式,闡述對此模式的理解。
在Java程序設(shè)計的過程中,經(jīng)常遇到拋出空指針異常的報錯信息,本文基于這種常見的問題,引入一種特殊的對象——空對象來避免這樣的問題。通過使用空對象模式,對一個簡單的圖書管理場景進行設(shè)計,使得程序更加友好和簡潔,并闡述空對象模式的實現(xiàn)方式及應(yīng)用場景,總結(jié)空對象模式的優(yōu)缺點分析與深入認識。
1 空對象模式
1.1 模式動機
在Java程序設(shè)計的過程中,經(jīng)常遇到調(diào)用空對象的方法拋出空指針異常的報錯信息,以及在客戶端經(jīng)常需要多次使用if語句檢查某一對象是否為空而導(dǎo)致非常多的冗余的檢查代碼,并且,在客戶端的判斷操作與動作使得主動權(quán)交給了客戶端,而不是系統(tǒng)本身,造成了程序設(shè)計缺乏優(yōu)雅與健壯性。
因此,引入空對象模式來更好地解決上述問題,使我們的代碼更加優(yōu)雅,程序更加健壯。
1.2 模式定義
按照目的來分,設(shè)計模式可以分為創(chuàng)建型模式、結(jié)構(gòu)型模式和行為型模式。
創(chuàng)建型模式用來處理對象的創(chuàng)建過程;結(jié)構(gòu)型模式用來處理類或者對象的組合;行為型模式用來對類或?qū)ο笤鯓咏换ズ驮鯓臃峙渎氊熯M行描述??諏ο竽J剿鶎俚念悇e及定義如下:
1.2.1 模式分類
空對象模式屬于三種設(shè)計模式中行為型模式的一種,也就是說模式特別關(guān)注對象之間的通信,如果對象的行為設(shè)計的好,那么對象的行為就會更清晰,它們之間的協(xié)作效率就會提高??梢栽谶M行對象間交流活動時增強彈性。
空對象模式引出空對象,它可以接收傳遞給它的所代表對象的信息,但是將返回表示為實際上并不存在任何“真實”的對象的值,是反應(yīng)一個不做任何動作的關(guān)系。通過這種方式,可以假設(shè)所有的對象都是有效的,而不必浪費巨大精力去檢查null。
1.2.2 模式定義
在空對象模式(Null Object Pattern)中,一個空對象取代NULL對象實例的檢查。Null對象不是檢查空值,而是反應(yīng)一個不做任何動作的關(guān)系。這樣的Null對象也可以在數(shù)據(jù)不可用的時候提供默認的行為。
空對象模式是一種提供智能的,不作為的行為,從它的合作者中隱藏細節(jié)。
由模式的定義我們可知,空對象實際上是為解決經(jīng)常拋出的空指針異常錯誤和代碼設(shè)計不合理問題而設(shè)計的一種特殊的對象。
1.3 類圖及實現(xiàn)
空對象模式提出一個空對象來代替null,防止空指針報錯對整個程序甚至整個系統(tǒng)的運行影響,獲取對空對象的控制,使系統(tǒng)較為穩(wěn)定的運行,以下是一個典型的空對象模式的類圖及實現(xiàn)。
1.3.1 類圖
典型空對象模式的類圖如圖1所示。
在圖中我們可以分析:空對象模式擁有一個抽象類,每種具體的類和一個空對象類與抽象類的關(guān)系是繼承關(guān)系,它們繼承此抽象類,并且實現(xiàn)其中的抽象方法;客戶端與抽象類之間為依賴關(guān)系,客戶端依賴于抽象類。
1.3.2 實現(xiàn)
針對上面的空對象模式的類圖,我們可以編寫如下代碼,來模擬空對象模式的實現(xiàn)。
首先是抽象類AbstractOperation,其中包括抽象方法request(),所有繼承它的子類都需要實現(xiàn)此方法。代碼如下:
public abstract class AbstractOperation
{
abstract void request();
}
RealOperation類為具體的類,繼承抽象類AbstractOperation,并且我們簡單地實現(xiàn)其中的request方法:
Public class RealOperation extends
AbstractOperation {
@Override
void request() {
System.out.println("do something");
}
}
NullOperation類則是本文所要重點介紹的——空對象類,此類繼承抽象類AbstractOperation,并且我們簡單地實現(xiàn)其中的request方法:
public class NullOperation extends
AbstractOperation{
@Override
void request() {
// do nothing
}
}
Client類為模擬的客戶端類,其中的func函數(shù)的功能是根據(jù)傳入的參數(shù)返回一個具體的RealOperation類對象或者NullOperation類對象;主函數(shù)中調(diào)用func函數(shù)傳參-1得到空對象,調(diào)用其中的request方法:
public class Client {
public static void main(String[] args) {
AbstractOperationabstractOperation = func(-1);
abstractOperation.request();
}
public static AbstractOperationfunc(int para) {
if (para < 0) {
return new NullOperation();
}
return new RealOperation();
}
}
運行結(jié)果如圖2所示(很好地防止空指針異常的錯誤):
經(jīng)過運行,發(fā)現(xiàn)此程序的設(shè)計方式可以很好地防止空指針異常的錯誤信息,且把代碼的主動權(quán)交給了系統(tǒng),使得代碼更加優(yōu)雅,可維護性更強。
注:若未使用空對象模式,在func函數(shù)中設(shè)置傳參小于0的條件下returnnull,則會出現(xiàn)空指針異常的錯誤信息,如圖3所示。
2 實例與解析
下面以圖書管理的場景為例,進一步講解空對象的設(shè)計方式與優(yōu)勢所在。
2.1 應(yīng)用場景
圖書館查詢系統(tǒng)應(yīng)該可以通過傳入圖書的ID來獲取指定的圖書對象,并調(diào)用此圖書對象中的方法得到此書信息。
具體的要求是這樣的:輸入圖書的ID,調(diào)用一個查詢的方法并在方法中傳入圖書的ID,然后系統(tǒng)會返回給你要查找的圖書對象,由此可以調(diào)用這個圖書對象的輸出圖書相關(guān)信息方法來輸出客戶需要的圖書信息。
2.2 傳統(tǒng)解決方式及不足
首先我們來簡單講述一下傳統(tǒng)的解決方式。
有一個圖書類,其中包括圖書ID和作者等其他圖書信息字段,包含了構(gòu)造函數(shù)和show函數(shù),用于展示圖書相關(guān)信息,具體代碼如下所示:
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;
}
public void show() {
System.out.println(ID + "**" + name + "**" + author);
}
}
需要有圖書工廠來創(chuàng)建圖書對象,通過傳入圖書的ID,使用switch語句選擇,返回相應(yīng)ID的圖書對象,具體代碼如下所示:
public class BookFactory {
public ConcreteBookgetBook(int ID) {
ConcreteBook book = null;
switch (ID) {
case 1:
book = new ConcreteBook(ID, "設(shè)計模式", "GoF");
break;
case 2:
book = new ConcreteBook(ID, "空對象模
式", "Null Object Pattern");
break;
default:
book = null;
break;
}
return book;
}
}
在客戶端輸入圖書ID為1,進行運行,具體代碼如下所示:
public class Client {
public static void main(String[] args) {
BookFactorybookFactory = new BookFactory();
ConcreteBook book = bookFactory.getBook(1);
book.show();
}
}
可以正常運行,如圖4所示:
但是,當我們傳入?yún)?shù)為-1時,bookFactory對象返回null,調(diào)用null的getBook方法將導(dǎo)致空指針異常的錯誤,如圖5所示。
有一種常見的解決方式,在客戶端加一個判斷,判斷是否為null:如果為null的話,就不再調(diào)用show()方法;如果不為null再調(diào)用show()方法。這樣的做法確實可以避免此問題的出現(xiàn),改進后的主函數(shù)代碼如下所示:
public class BookFactory {
public static void main(String[] args) {
BookFactorybookFactory = new BookFactory();
ConcreteBook book = bookFactory.getBook(-1);
if (book == null) {
System.out.println("book對象為 null。");
} else {
book.show();
}
}
這樣做確實消除了報錯,但是卻存在很多弊端:
(1)如果在一段程序中有很多處調(diào)用getBook()方法或者有很多個客戶端,那么很多處都要判斷book對象是否為null。
(2)如果有一處沒有判斷,發(fā)生報錯,很有可能導(dǎo)致程序沒法繼續(xù)運行甚至崩潰。
(3)這樣做還把整個程序的穩(wěn)定性寄托在客戶端身
上,這樣的處理方法,當獲取對象為null的時候,輸出的提示信息是由客戶端來定制的,這樣把主動權(quán)交給了客戶端,而不是我們系統(tǒng)本身。
2.3 使用空對象模式解決
基于傳統(tǒng)解決方式的弊端,提出用空對象模式來解決此問題。
引入接口類Book,其中isNull方法判斷Book對象是否為空對象,show展示Book對象的信息內(nèi)容,Book類的代碼如下所示:
interface Book {
public booleanisNull();
public void show();
}
空對象類NullBook和具體書類ConcreteBook實現(xiàn)此接口。
空對象類NullBook的代碼如下所示:
public class NullBook implements Book {
public booleanisNull() {
return true;
}
public void show() {
System.out.println("Sorry,未找到符合您輸入的ID的圖書信息,請確認您輸入的不是非法值。");
}
}
ConcreteBook類在原有的ConcreteBook類的基礎(chǔ)上,增加對Book接口的實現(xiàn),實現(xiàn)其中的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;
}
public void show() {
System.out.println(ID + "**" + name + "**" + author);
}
public booleanisNull(){
return false;
}
}
BookFactory類中的方法根據(jù)輸入的參數(shù)創(chuàng)建對應(yīng)的對象,返回對象從ConcreteBook改為Book,這也體現(xiàn)了我們面向抽象編程的思想,具體代碼如下所示:
public class BookFactory {
public Book getBook(int ID) {
Book book;
switch (ID) {
case 1:
book = new ConcreteBook(ID, "設(shè)計模式", "GoF");
break;
case 2:
book = new ConcreteBook(ID, "空對象模式", "Null Object Pattern");
break;
default:
book = new NullBook();
break;
}
return book;
}
}
客戶端主函數(shù)代碼也進行對應(yīng)的修改:
public static void main(String[] args) {
BookFactorybookFactory = new BookFactory();
Book book = bookFactory.getBook(-1);
book.show();
}
執(zhí)行一下Client,可以發(fā)現(xiàn)控制臺輸出為:Sorry,未找到符合您輸入的ID的圖書信息,請確認您輸入的不是非法值。詳細運行結(jié)果如圖6所示:
我們發(fā)現(xiàn),此時,即使傳入的參數(shù)是非法值或者不存在的值時,也不會報錯。并且,把判斷條件從客戶端轉(zhuǎn)移到了系統(tǒng)中,實現(xiàn)了一處定制,處處輸出。體現(xiàn)了空對象模式在此應(yīng)用環(huán)境下強大的優(yōu)越性。
我們可以得到使用了空對象模式的類圖如圖7所示:
當然,雖然在客戶端不進行檢測也可以保證程序不報錯,但是我們也可以在客戶端進行相應(yīng)的檢測。使用if判斷book.isNull(),而非之前的book == null判斷,同樣可以對客戶端的進行設(shè)計,相比之下,book.isNull()比book == null更優(yōu)雅,思路類似,這里便不過多贅述。
2.4 兩種解決方式的比較
對于圖書管理場景的傳統(tǒng)實現(xiàn)方式和空對象模式兩種實現(xiàn)方式的分析,我們可以對兩種解決方式進行比較:
(1)傳統(tǒng)的解決方式則容易產(chǎn)生空指針異常的錯誤信息;空對象模式能有效地防止空指針報錯對整個系統(tǒng)的影響,使系統(tǒng)更加穩(wěn)定,即使傳入的參數(shù)是非法值或者不存在的值時,也不會報錯。
(2)傳統(tǒng)的方式主動權(quán)在客戶端;使用空對象模式能夠?qū)崿F(xiàn)對空對象情況的定制化的控制。
(3)傳統(tǒng)的解決方式使用==null判空;而空對象模式通過isNull判空,顯得更加優(yōu)雅,更加易懂。
3 效果與分析
3.1 應(yīng)用場景
空對象模式是程序或系統(tǒng)中對null檢查過多,為了增強代碼的魯棒性和可讀性使用。可適用于不想把對null的檢查交給客戶端,降低系統(tǒng)的安全性時使用。
更為主要的是空對象模式是適用在對null檢查后,期望做的事情是一個不做任何動作的關(guān)系時,這時能使用空對象模式。
3.2 遵循的設(shè)計原則
在軟件設(shè)計和開發(fā)中,為了提高軟件系統(tǒng)的可維護性和可復(fù)用性,增加軟件的可擴展性和靈活性,需要盡量根據(jù)七條原則來開發(fā)程序,從而提高軟件開發(fā)效率、節(jié)約軟件開發(fā)成本和維護成本。
空對象模式符合單一職責原則、依賴倒置原則、迪米特法則、開閉原則,因此能較好的提高程序和系統(tǒng)的穩(wěn)定性。
3.3 空對象模式優(yōu)點
由上述分析可知,空對象模式具有如下優(yōu)點:
(1)空對象模式符合軟件設(shè)計的基本原則,可以加強系統(tǒng)的穩(wěn)固性,能夠有效地防止空指針報錯對整個系統(tǒng)的影響,使系統(tǒng)更加穩(wěn)定。
(2)空對象模式能夠?qū)崿F(xiàn)對空對象情況的定制化的控制,能夠掌握處理空對象的主動權(quán),主動權(quán)在于系統(tǒng),而不是客戶端。
(3)空對象模式并不依靠Client客戶端來保證整個系統(tǒng)的穩(wěn)定運行。
(4)空對象模式可以降低“錯誤處理”開銷,不至于經(jīng)常出現(xiàn)空指針異常報錯的問題,使程序的穩(wěn)定性更好。
3.4 空對象模式缺點
雖然空對象模式有很多優(yōu)點,但是我們也不難發(fā)現(xiàn)它具有一定的缺點:
(1)如果各種客戶端對null對象應(yīng)該如何做都不可以達成共識(如未正確定義AbstractObject接口時),則可能難以實現(xiàn)。
(2)可能需要為每個新的AbstractObject類創(chuàng)建一個
新的NullObject類,會增加類以及結(jié)構(gòu)和層次的復(fù)雜性。
(3)使用時不知道是空值,如果接口類的需要使用異常等則不能使用。
4 結(jié)論
4.1 空對象模式總結(jié)
空對象模式是常用的軟件設(shè)計模式以外的模式,但是對于開發(fā)者以及系統(tǒng)本身而言,這種設(shè)計模式都是很有利的,空對象模式可以使編程更加方便,使代碼更加規(guī)范,使系統(tǒng)更加安全。
本文針對在編程的過程中,每次使用引用時,需要大量繁瑣的代碼測試其是否為空,且把主動權(quán)交給客戶而非系統(tǒng)本身,在Java編程過程中NullPointerException空指針異常的報錯信息的經(jīng)常出現(xiàn),會導(dǎo)致系統(tǒng)出現(xiàn)異常甚至停機的情況,由此提出了空對象模式的一個應(yīng)用,而在實際系統(tǒng)出現(xiàn)空的情況下,系統(tǒng)即使出現(xiàn)錯誤后也不應(yīng)該因為客戶端請求為空而停機,而是應(yīng)該返回相應(yīng)的空對象以避免系統(tǒng)的崩潰,因此在大多數(shù)實際場景中,空對象是一個良好的應(yīng)對策略。
4.2 設(shè)計模式總結(jié)
總的來說,設(shè)計模式主要是分為三大類:創(chuàng)建型模式、結(jié)構(gòu)型模式、行為型模式,其實還有并發(fā)型模式和線程池模式。在這些模式中都提供了對應(yīng)問題的核心解決方案,都是沿用了“不是解決任何問題都需要從頭做起”的思想,都是針對接口編程,而不是針對實現(xiàn)編程,都是優(yōu)先使用組合而不是類繼承,都是把設(shè)計去支持變化,有著對程序和系統(tǒng)的預(yù)見性。
在我們對一個設(shè)計模式的應(yīng)用之前,都應(yīng)該考慮這個模式能解決什么問題,采用這個模式解決是否是最佳方案,是否有著想得到的效果等。每一種設(shè)計模式都是建立在對系統(tǒng)變化點的基礎(chǔ)上進行,我們在面對新問題時,其設(shè)計模式也應(yīng)以演化的方式來獲得,通過不斷的改進才得以準確定位。
最后,設(shè)計模式體現(xiàn)的是一種思想,而思想是指導(dǎo)行為的一切,理解和掌握了設(shè)計模式,并不代表獲得了設(shè)計模式的一切,更多接受的是一種思想的熏陶和洗禮,把思想融入實際的設(shè)計與開發(fā)中去,才是最為重要的。
參考文獻:
[1]張揚嵩.Java程序設(shè)計中空對象的應(yīng)用[J].電腦編程技巧與維護,2011(13):87-88.
[2]溫立輝.軟件設(shè)計模式分析[J].科技創(chuàng)新與應(yīng)用,2020(7):92-93.
[3]鄭苗.基于Java的設(shè)計模式理解與實現(xiàn)[J].電腦知識與技術(shù),2017,13(032):115-116.
[4]邱士超.被遺忘的設(shè)計模式——空對象模式[EB/OL].https://blog.csdn.net/qiumengchen12/article/details/44923139.
[5]楊俊峰.軟件設(shè)計模式的最佳實踐探索[J].中外企業(yè)家,2019(27):201.
[6]Peter Vogt, Landscape Ecology. Patterns in software design[J].2019,34(9):2083-2089.