在一些老項目中,咱們應該時常會碰到以下場景(若是碰不到能夠不用繼續看了):一些類的一些方法體中,有針對同一個變量進行判空的邏輯。 若是這些方法體內須要判空的方法很少,那還好說,若是一旦出現多數方法都要針對一個變量進行判空,那麼這些代碼每每就難以維護了。java
例如:git
這種大量針對同一個變量進行判空的方法,會帶來的缺點以下(仔細品味缺點是理解後續改進的重要基礎):編程
來看個具體的例子,電商項目中,咱們處理支付總會有一個統一的出入口,支付的行爲有許多種,好比常見的就是用券和不用券。數組
public class PayProcess {
private CouponInfo couponInfo;
public void setCouponInfo(CouponInfo couponInfo) {
this.couponInfo = couponInfo;
}
//檢查支付的合法性
public boolean checkLegitimate() {
if (null != couponInfo) {
//其實這裏主要就是檢查一下券有沒有過時
return couponInfo.checkLeg();
}
//若是沒有券 就意味着合法
return true;
}
//獲取實際支付金額
public int getPayValue(int totalValue) {
if (null != couponInfo) {
//支付總金額 減去 券的金額 天然就是須要支付的金額
return totalValue - couponInfo.getCouponValue();
}
return totalValue;
}
//獲取優惠券的類型
public int getCouponType() {
if (null != couponInfo) {
return couponInfo.getCouponType();
}
//若是壓根就沒有優惠券 這裏就返回0 0表明沒有優惠券, 實際業務中 咱們不能寫這種魔法數字
//必定要定義成常量,這裏爲了演示方便 我就偷懶了
return 0;
}
}
class CouponInfo {
public boolean checkLeg() {
//實際中 咱們會校驗券的時間 等等,如今爲了演示方便 我就直接返回一個false了
//你們知道意思就好
return false;
}
public int getCouponValue() {
//返回券的實際價值,這裏也是爲了演示方便 我直接返回一個固定值
return 3;
}
public int getCouponType() {
//返回券的種類,看看是無敵券?仍是限定品類的券 等等
//爲了演示方便 我直接寫一個int值,實際寫的時候 必定要寫成常量
return 2;
}
}
複製代碼
當咱們使用這個支付系統的時候,確定會有多種使用狀況,有些場景用了券,有些場景沒有用。例如:bash
public static void main(String[] args) {
//這裏是用券的
PayProcess p1 = new PayProcess();
p1.setCouponInfo(new CouponInfo());
//這裏是沒有用券的
PayProcess p2 = new PayProcess();
p1.setCouponInfo(null);
}
複製代碼
肉眼可見的,咱們的PayProcess要寫不少判空的代碼。 防護式編程總沒有壞處。這也是阿里java開發手冊中提到的重要的一點,該判空的要判空,該斷定數組越界的要數組越界。思路是沒錯的,可是相似這樣的代碼,很容易就陷入了判空地獄。出現咱們文章開頭說的哪些缺點。ui
如何重構這部分老代碼?讓他看起來不是這麼糟糕?this
咱們新增一個類(其實主要目的就是在這裏統一處理爲null的狀況):spa
//這裏面的邏輯 注意看 其實和PayProcess 裏面當券爲null的時候邏輯同樣的
public class NullCouponInfo extends CouponInfo {
public boolean checkLeg() {
return true;
}
//沒有券 那券的價值就爲0
public int getCouponValue() {
return 0;
}
// 沒有券 天然type爲0
public int getCouponType() {
return 0;
}
}
複製代碼
而後咱們的支付類 就能夠清爽不少:設計
public class PayProcess {
private CouponInfo couponInfo;
public void setCouponInfo(CouponInfo couponInfo) {
this.couponInfo = couponInfo;
}
//檢查支付的合法性
public boolean checkLegitimate() {
//其實這裏主要就是檢查一下券有沒有過時
return couponInfo.checkLeg();
}
//獲取實際支付金額
public int getPayValue(int totalValue) {
//支付總金額 減去 券的金額 天然就是須要支付的金額
return totalValue - couponInfo.getCouponValue();
}
//獲取優惠券的類型
public int getCouponType() {
return couponInfo.getCouponType();
}
}
複製代碼
最後調用的時候,當遇到券爲空的時候 就不要傳null做爲參數了指針
PayProcess p3 = new PayProcess();
p1.setCouponInfo(new NullCouponInfo());
複製代碼
你看這樣一改完,整個邏輯上就清晰不少,可讀性也很好。也沒有那麼多重複的代碼。固然這裏還有一個隱患: 當咱們券須要新增一些方法的時候,咱們除了要改CouponInfo 還須要改NullCouponInfo,若是改漏了,那麼就會在PayProcess類中 留下隱患。雖然不會有空指針異常,可是每每不會獲得咱們想要的結果。
針對這種場景,其實咱們只要抽象出一個接口便可。讓咱們的CouponInfo和NullCouponInfo 都繼承一個接口: ICoupon(不要再讓CouponInfo做爲NullCouponInfo的父類),這樣全部新增的方法只須要在接口裏面增長便可,這樣編譯的時候就會提示咱們在2個子類中都須要實現。 從而規避掉上述的隱患。(這裏代碼比較簡單就不演示了)
總結: 將全部的null邏輯 替換爲一個null object,就能夠解決咱們文章開始時拋出的問題。有一些要點以下: