重構手法之簡化條件表達式【4】

来源:http://www.cnblogs.com/liuyoung/archive/2017/11/29/7895727.html
-Advertisement-
Play Games

返回總目錄 7 Introduce Null Object(引入Null對象) 概要 你需要再三檢查某對象是否為null。 將null值替換為null對象。 動機 系統在使用對象的相關功能時,總要檢查對象是否為null,如果不為null,我們才會調用它的相關方法,完成某種邏輯。這樣的檢查在一個系統中 ...


返回總目錄

7 Introduce Null Object(引入Null對象)

概要

你需要再三檢查某對象是否為null。

將null值替換為null對象。

動機

系統在使用對象的相關功能時,總要檢查對象是否為null,如果不為null,我們才會調用它的相關方法,完成某種邏輯。這樣的檢查在一個系統中出現很多次,相信任何一個設計者都不願意看到這樣的情況。為瞭解決這種問題,我們可以引入空對象,這樣,我們就可以擺脫大量程式化的代碼,對代碼的可讀性也是一個飛躍。

範例

以下代碼中,Site類表示地點。任何時候每個地點都擁有一個顧客,顧客信息以Customer表示:

public class Site
{
    public Customer Customer { get; set; }
}

public class Customer
{
    public string Name { get; set; }
    public BillingPlan Plan { get; set; }
    public PaymentHistory History { get; set; }
}

public class BillingPlan
{
    public int BasicPay { get; set; }

    public BillingPlan()
    {
        BasicPay = 0;
    }
    public BillingPlan(int pay)
    {
        BasicPay = pay;
    }
    public static BillingPlan Basic()
    {
        return new BillingPlan(100);
    }
}
public class PaymentHistory
{
    public int GetWeekDelinquentInLastYear()
    {
        return 100;
    }
}

我們可能會這樣調用:

Site site = new Site();
Customer customer = site.Customer;
BillingPlan plan;
if (customer == null)
{
    plan = BillingPlan.Basic();
}
else
{
    plan = customer.Plan;
}
string customerName;
if (customer == null)
{
    customerName = "occupant";
}
else
{
    customerName = customer.Name;
}
int weeksDelinquent;
if (customer == null)
{
    weeksDelinquent = 0;
}
else
{
    weeksDelinquent = customer.History.GetWeekDelinquentInLastYear();
}

這個系統中可能會有許多地方使用Site和Customer對象,它們都必須檢查Customer對象是否等於null,而這樣的檢查完全是重覆的。下麵使用空對象進行重構。

首先新建一個NullCustomer,並修改Customer,使其支持“對象是否為null”的檢查:

public class Customer 
{
    public virtual bool IsNull()
    {
        return false;
    }
}
class NullCustomer : Customer
{
    public override bool IsNull()
    {
        return true;
    }
}

接下來,在Customer中加入一個函數,專門用來創建NullCustomer對象。這樣一來,用戶就不必知道空對象的存在了:

public class Customer
{
     public static Customer NewNull()
    {
        return new NullCustomer();
    }
}

對於所有提供Customer對象的地方,將他們都加以修改,使它們不能返回null,而返回一個NullCustomer對象。

public class Site
{
    private Customer _customer;
    public Customer Customer
    {
        get => _customer ?? Customer.NewNull();
        set => _customer = value;
    }
}

另外,還要修改所有使用Customer對象的地方,讓它們以IsNull()函數進行檢查,不再使用==null的檢查方式。

Site site = new Site();
Customer customer = site.Customer;
BillingPlan plan;
if (customer.IsNull())
{
    plan = BillingPlan.Basic();
}
else
{
    plan = customer.Plan;
}
string customerName;
if (customer.IsNull())
{
    customerName = "occupant";
}
else
{
    customerName = customer.Name;
}
int weeksDelinquent;
if (customer.IsNull())
{
    weeksDelinquent = 0;
}
else
{
    weeksDelinquent = customer.History.GetWeekDelinquentInLastYear();
}

但是到目前為止,使用IsNull()函數尚未帶來任何好處。下麵就把相關行為移到NullCustomer中並去除條件表達式。

首先為NullCustomer加入一個合適的函數,通過這個函數來取得顧客名稱,為此先將Customer中的屬性改為虛屬性,這樣方便在子類中重寫:

class NullCustomer : Customer
{
    public override string Name => "occupant";

public override bool IsNull() { return true; } }

現在,調用的時候就可以去除條件代碼了,下麵的代碼:

if (customer.IsNull())
{
    customerName = "occupant";
}
else
{
    customerName = customer.Name;
}

現在變成這樣調用:

string customerName=customer.Name;

接下來以相同的手法處理其他函數,使它們對相應的查詢做出最合適的響應。於是下麵這樣的客戶端程式:

BillingPlan plan;
if (customer.IsNull())
{
    plan = BillingPlan.Basic();
}
else
{
    plan = customer.Plan;
}

就變成了這樣:

BillingPlan plan = customer.Plan;
class NullCustomer : Customer
{
    public override BillingPlan Plan => BillingPlan.Basic();

}

這裡註意一下:只有當大多數客戶代碼都要求使用空對象做出相同響應時,這樣的行為搬移才有意義。請註意,是“大多數”而不是“所有”。任何用戶如果需要空對象做出不同響應,仍然可以使用IsNull()函數來測試。只要大多數客戶端都要求空對象做出相同響應,就可以調用預設的Null行為。

上述例子中略有差異的是下麵這段代碼:

 int weeksDelinquent;
 if (customer.IsNull())
 {
     weeksDelinquent = 0;
 }
 else
 {
     weeksDelinquent = customer.History.GetWeekDelinquentInLastYear();
 }

我們可以新建一個NullPaymentHistory類,用以處理這種情況:

class NullPaymentHistory : PaymentHistory
{
    public override int GetWeekDelinquentInLastYear()
    {
        return 0;
    }
}
public class PaymentHistory
{
    public virtual int GetWeekDelinquentInLastYear()
    {
        return 100;
    }

    public static PaymentHistory NewNull()
    {
        return new NullPaymentHistory();
    }
}

現在來修改NullCustomer,讓其返回一個NullPaymentHistory對象:

class NullCustomer : Customer
{
    public override string Name => "occupant";

    public override BillingPlan Plan => BillingPlan.Basic();

    public override PaymentHistory History => PaymentHistory.NewNull();

    public override bool IsNull()
    {
        return true;
    }

}

然後,就可以刪除這一行的條件代碼:

int weeksDelinquent = customer.History.GetWeekDelinquentInLastYear();

可以看到,使用Null對象後,代碼結構很清晰,代碼量大大減少。

小結

空對象通過isNull對==null的替換,顯得更加優雅,更加易懂;並不依靠Client來保證整個系統的穩定運行同時能夠實現對空對象情況的定製化的控制,能夠掌握處理空對象的主動權。

階段性小結

條件邏輯有可能十分複雜,因此重構手法之簡化條件表達式第1-7小節提供重構手法,專門用來簡化它們。其中一項核心重構就是Decompose Conditional,可將一個複雜的條件邏輯分成若幹小塊。這項重構很重要,因為它使得“分支邏輯”和“操作細節”分離。

如果代碼中有多次測試有相同結果,應該實施Consolidate Conditional Expression;如果條件代碼中有任何重覆,可以運用Consolidate Duplicate Conditional Fragments將重覆成分去掉。

如果程式開發者堅持“單一齣口”原則,那麼為讓條件表達式也遵循這一原則,他往往會在其中加入控制標記,可以使用Replace Nested Conditional with Guard Clauses標示出那些特殊情況,並使用Remove Control Flag去除那些討厭的控制標記。

在面向對象程式中如果出現switch語句,可以考慮運用Replace Conditional with Polymorphism將它替換為多態。

多態還有一種十分有用但鮮為人知的用途:通過Introduce Null Object去除對於null值的檢驗。

 

 

To Be Continued……


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在基於傳統的.NET Framework的Asp.Net Mvc的時候,本地開發環境中可以在IIS中建立一個站點,可以直接把站點的目錄指向asp.net mvc的項目的根目錄。然後build一下就可以在瀏覽器裡面刷新到最新的修改了,也可以附加到w3wp的進程進行調試。但是在開發基於.Net Core ...
  • Redis請求協議的一般形式: 備註:CR表示為\r; LF表示\n 下麵是一個例子: Redis回覆 Redis 命令會返回多種不同類型的回覆。 通過檢查伺服器發回數據的第一個位元組, 可以確定這個回覆是什麼類型: 狀態回覆(status reply)的第一個位元組是 "+" 錯誤回覆(error r ...
  • 生命周期函數:需要繼承 MonoBehaviour 類才能使用。生命周期函數全部都是由系統定義好的,系統會自動調用,且調用順序和我們在代碼裡面的書寫順序無關。 常用的生命周期函數: Awake():喚醒事件,游戲一開始運行就執行,只執行一次。 OnEnable():啟用事件,只執行一次。當腳本組件被 ...
  • hangfire是執行後臺任務的利器,具體請看官網介紹:https://www.hangfire.io/ 新建一個asp.net core mvc 項目 引入nuget包 Hangfire.AspNetCore hangfire的任務需要資料庫持久化,我們在Startup類中修改ConfigureS ...
  • 實習狗的每天新知識日常 準備工作: 1.在項目中添加對NPOI的引用,NPOI下載地址:http://npoi.codeplex.com/releases/view/38113 2.NPOI學習系列教程推薦:http://www.cnblogs.com/tonyqus/archive/2009/04 ...
  • 紙殼CMS可以運行在Docker上,接下來看看如何自動構建紙殼CMS的Docker Image。我們希望的是在代碼提交到GitHub以後,容器鏡像服務可以自動構建Docker Image,構建好以後,就可以直接拿這個Docker Image來運行了。 ...
  • EF Power Tools最高支持到vs2013,但是可以通過變更版本號的方式在vs2015上進行安裝。 參考文章:(英文)http://thedatafarm.com/data-access/installing-ef-power-tools-into-vs2015/ (中文)https://w ...
  • 演示產品下載地址:http://www.jinhusns.com ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...