重構手法之簡化條件表達式【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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...