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

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

返回總目錄 本小節目錄 Replace Nested Conditional with Guard Claues(以衛語句取代嵌套條件表達式) Replace Conditional with Polymorphism(以多態取代條件表達式) 5Replace Nested Conditional ...


返回總目錄

本小節目錄

5Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件表達式)

概要

函數中的條件邏輯使人難以看清正常的執行路徑。

使用衛語句表現所有特殊情況。

動機

條件表達式通常有兩種表現形式。(1)所有分支都屬於正常行為;(2)條件表達式提供的答案中只有一種是正常行為,其他都是不常見的情況。

如果兩條分支都是正常行為,就應該使用形如if...else...的條件表達式;如果某個條件極其罕見,就應該單獨檢查該條件,併在該條件為真時立刻從函數中返回。這樣的單獨檢查被稱為“衛語句”。

該重構手法的精髓就是:給一條分支以特別的重視。

範例

下麵的函數以特殊規則處理死亡員工、駐外員工、退休員工的薪資:

class Amount
{
    public bool IsDead { get; set; }

    public bool IsSeparated { get; set; }

    public bool IsRetired { get; set; }
public double GetPayAmount() { double result; if (IsDead) { result = DeadAmount(); } else { if (IsSeparated) { result = SeparatedAmount(); } else { if (IsRetired) { result = RetiredAmount(); } else { result = NormalAmount(); } } } return result; } private double DeadAmount() { return default(double); } private double SeparatedAmount() { return default(double); } private double RetiredAmount() { return default(double); } private double NormalAmount() { return default(double); } }

我們可以看到,這段代碼中,非正常情況掩蓋了正常情況的檢查,所以應該用衛語句來取代這些檢查,以提高程式清晰度。

我們從最上面的條件動作開始:

public double GetPayAmount()
{
    double result;
    if (IsDead)
    {
        return DeadAmount();
    }
    if (IsSeparated)
    {
        result = SeparatedAmount();
    }
    else
    {
        if (IsRetired)
        {
            result = RetiredAmount();
        }
        else
        {
            result = NormalAmount();
        }
    }
    return result;
}

然後繼續替換下一個:

public double GetPayAmount()
{
    double result;
    if (IsDead)
    {
        return DeadAmount();
    }
    if (IsSeparated)
    {
        return SeparatedAmount();
    }
    if (IsRetired)
    {
        result = RetiredAmount();
    }
    else
    {
        result = NormalAmount();
    }

    return result;
}

然後是最後一個:

public double GetPayAmount()
{
    double result;
    if (IsDead)
    {
        return DeadAmount();
    }
    if (IsSeparated)
    {
        return SeparatedAmount();
    }
    if (IsRetired)
    {
        return RetiredAmount();
    }
    result = NormalAmount();
    return result;
}

此時,result變數已經沒有意義了,所以可以刪掉,最終代碼如下:

class Amount
{
    public bool IsDead { get; set; }

    public bool IsSeparated { get; set; 

    public bool IsRetired { get; set; }
    public double GetPayAmount()
    {
        if (IsDead)
        {
            return DeadAmount();
        }
        if (IsSeparated)
        {
            return SeparatedAmount();
        }
        if (IsRetired)
        {
            return RetiredAmount();
        }
        return NormalAmount();
    }
    private double DeadAmount()
    {
        return default(double);
    }
    private double SeparatedAmount()
    {
        return default(double);
    }

    private double RetiredAmount()
    {
        return default(double);

    }
    private double NormalAmount()
    {
        return default(double);
    }
}

 範例:將條件反轉

class AdjustedCapital
{
    public double Capital { get; set; }
public double IntRate { get; set; } public double Income { get; set; } public double Duration { get; set; } public double GetAdjustedCapital() { double result = 0; if (Capital > 0) { if (IntRate > 0 && Duration > 0) { result = Income / Duration; } } return result; } }

同樣地,我們進行逐一替換。不過在插入衛語句時,需要將條件反轉過來:

public double GetAdjustedCapital()
{
    double result = 0;
    if (Capital <= 0)
    {
        return result;
    }
    if (IntRate > 0 && Duration > 0)
    {
        result = Income / Duration;
    }
    return result;
}

再替換下一個:

public double GetAdjustedCapital()
{
    double result = 0;
    if (Capital <= 0)
    {
        return result;
    }
    if (IntRate <= 0 || Duration <= 0)
    {
        return result;
    }
    result = Income / Duration;
    return result;
}

最後,我們可以刪除臨時變數:

public double GetAdjustedCapital()
{
    if (Capital <= 0)
    {
        return 0;
    }
    if (IntRate <= 0 || Duration <= 0)
    {
        return 0;
    }
    return Income / Duration;
}

 小結

許多程式員都有這樣一個觀念:“每個函數只能有一個入口和一個出口。”現代編程語言都會限制函數只有一個入口。但“函數只有一個出口”,其實並不是那麼管用。

書中有這麼一句話:嵌套的條件分支往往是由一些深信“每個函數只能有一個出口的”程式員寫出的。但實際上,如果對函數的剩餘部分不感興趣,那就應該立即退出。 引導閱讀者去看一些沒有用的else片段,只會妨礙他們對程式的理解。

6Replace Conditional with Polymorphism(以多態取代條件表達式)

概要

你手上有個條件表達式,它根據對象類型的不同而選擇不用的行為。

將這個條件表達式的每個分支放進一個子類內的覆寫函數中,然後將原始函數聲明為抽象函數。

動機

如果需要根據對象的不同類型而採取不同的行為,使用多態可以不用編寫明顯的條件表達式。

有一組條件表達式,如果想添加一種新類型,就必須查找並更新所有的條件表達式。而使用多態,只需要建立一個新的子類,並提供適當的函數即可。這就大大降低了系統各部分之間的依賴,使系統升級更加容易。

範例

如下代碼所示,OrderProcessor類的ProcessOrder方法根據Customer的類型分別執行一些操作:

public abstract class Customer
{
}

public class Employee : Customer
{
}

public class NonEmployee : Customer
{
}
public class Product
{
    public int Price { get; set; }
}
public class OrderProcessor
{
    public decimal ProcessOrder(Customer customer, IEnumerable<Product> products)
    {
        // do some processing of order
        decimal orderTotal = products.Sum(p => p.Price);

        Type customerType = customer.GetType();
        if (customerType == typeof(Employee))
        {
            orderTotal -= orderTotal * 0.15m;
        }
        else if (customerType == typeof(NonEmployee))
        {
            orderTotal -= orderTotal * 0.05m;
        }
        return orderTotal;
    }
}

重構後的代碼如下,每個Customer子類都封裝自己的演算法,然後OrderProcessor類的ProcessOrder方法的邏輯也變得簡單並且清晰了。

public abstract class Customer
{
    public abstract decimal DiscountPercentage { get; }
}

public class Employee : Customer
{
    public override decimal DiscountPercentage => 0.15m;
}

public class NonEmployee : Customer
{
    public override decimal DiscountPercentage => 0.05m;
}
public class Product
{
    public int Price { get; set; }
}
public class OrderProcessor
{
    public decimal ProcessOrder(Customer customer, IEnumerable<Product> products)
    {
        // do some processing of order
        decimal orderTotal = products.Sum(p => p.Price);

        orderTotal = orderTotal * customer.DiscountPercentage;

        return orderTotal;
    }
}

小結

“以多態取代條件表達式”這個重構在很多時候會出現設計模式中(常見的工廠家族、策略模式等都可以看到它的影子),因為運用它可以省去很多的條件判斷,同時也能簡化代碼、規範類和對象之間的職責。

 

 

To Be Continued……


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

-Advertisement-
Play Games
更多相關文章
  • haproxy實現高級負載均衡實戰 環境:隨著公司業務的發展,公司負載均衡服務已經實現四層負載均衡,但業務的複雜程度提升,公司要求把mobile手機站點作為單獨的服務提供,不在和pc站點一起提供服務,此時需要做7層規則負載均衡,運維總監要求,能否用一種服務同既能實現七層負載均衡,又能實現四層負載均衡 ...
  • 1. 添加 nginx 的 yum 源(官網安裝說明) vi /etc/yum.repos.d/nginx.repo 在該文件中添加如下內容: [nginx]name=nginx repobaseurl=http://nginx.org/packages/centos/$releasever/$ba ...
  • 正則表達式可以用來處理大量的文件和字元串,運維工作中過濾日記簡單高效,Linux最常應用正則表達式的命令有grep(egrep)、sed、awk。 正則表達式和文本通配符不同容易混淆。 字元匹配 . 匹配任意單個字元 [] 匹配指定範圍內的任意單個字元 [^] 匹配指定範圍外的任意單個字元 [:al ...
  • Linux的特點 1.免費的、開源的 2.支持多線程。多用戶的 3.安全性好 4.對記憶體和文件管理優越 5.linux最小隻需要4m->嵌入式開發 缺點: 操作相對困難 命令: shutdown -h now 立即進行關機 shutdown -r now 現在重新啟動電腦 reboot 現在重新啟 ...
  • 版本: Windows_server_2012-r2_x64 工具: VMware vSphere Client 鏡像地址: http://www.xpgod.com/soft/10718.html(地址為參考,可根據需求自行下載~) 首先鏈接虛機 鏈接成功後將本地資源進行上傳~ 滑鼠右擊datab ...
  • 從線程執行任務的方式上可以分為線程同步和線程非同步。而為了方便理解,後面描述中用“同步線程”指代與線程同步相關的線程,同樣,用“非同步線程”表示與線程非同步相關的線程。 線程非同步就是解決類似前面提到的執行耗時任務時界面控制項不能使用的問題。如創建一個次線程去專門執行耗時的任務,而其他如界面控制項響應這樣的任務 ...
  • var num = 1; var str = '1'; var test = 1; test == num //true 相同類型 相同值 test num //true 相同類型 相同值 test !== num //false test與num類型相同,其值也相同, 非運算肯定是false nu ...
  • Delphi DataTypeC# datatypeansistringstringbooleanboolbytebytecharcharcompdoublecurrencydecimaldoubledoubleextendeddoubleint64longint32intint16shortint... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...