返回總目錄 本小節目錄 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……