重構——條件邏輯判斷


1.案例分析

如何去除If,else,switch條件判斷

對於具有一定復雜邏輯的代碼實現,避免不了出現if,else,switch等邏輯判斷。當邏輯分支越來越多的時候,大大地加大了閱讀的難度。這種情況,我們該如何處理呢?

2.switch 與if else誰快

對同一個變量的不同值作條件判斷時,可以用switch語句與if語句,哪個語句執行效率更高呢,答案是switch語句,尤其是判斷的分支越多越明顯。(具體測試的代碼,小伙伴可以試一下)
    public static void main(String[] args) { testIF("12"); testSwitch("12"); } public static void testIF(String arg) { long t1 = System.nanoTime(); if ("1".equals(arg)) { System.out.println(arg); } else if ("2".equals(arg)) { System.out.println(arg); } else if ("3".equals(arg)) { System.out.println(arg); } else if ("4".equals(arg)) { System.out.println(arg); } else if ("5".equals(arg)) { System.out.println(arg); } else if ("6".equals(arg)) { System.out.println(arg); } else if ("7".equals(arg)) { System.out.println(arg); } else if ("8".equals(arg)) { System.out.println(arg); } else if ("9".equals(arg)) { System.out.println(arg); } else if ("10".equals(arg)) { System.out.println(arg); } else if ("11".equals(arg)) { System.out.println(arg); } else if ("12".equals(arg)) { System.out.println(arg); } else if ("13".equals(arg)) { System.out.println(arg); } else if ("14".equals(arg)) { System.out.println(arg); } else { System.out.println(arg); } long t2 = System.nanoTime(); System.out.println("test if : " + (t2 - t1)); } public static void testSwitch(String arg) { long t1 = System.nanoTime(); switch (arg) { case "1": System.out.println(arg); break; case "2": System.out.println(arg); break; case "3": System.out.println(arg); break; case "4": System.out.println(arg); break; case "5": System.out.println(arg); break; case "6": System.out.println(arg); break; case "7": System.out.println(arg); break; case "8": System.out.println(arg); break; case "9": System.out.println(arg); break; case "10": System.out.println(arg); break; case "11": System.out.println(arg); break; case "12": System.out.println(arg); break; case "13": System.out.println(arg); break; case "14": System.out.println(arg); break; default: System.out.println(arg); break; } long t2 = System.nanoTime(); System.out.println("test switch: " + (t2 - t1)); }
最終現實結果
12
test if : 482713
12
test switch: 24870

3.邏輯分支多為什么看起來費勁呢?

復雜!復雜!代碼圈復雜度高!
什么是代碼圈復雜度?

圈復雜度

1)概念:
  • 用來衡量一個模塊判定結構的復雜程度,數量上表現為獨立現行路徑條數,即合理的預防錯誤所需測試的最少路徑條數,圈復雜度大說明程序代碼可能質量低且難於測試和維護,根據經驗,程序的可能錯誤和高的圈復雜度有着很大關系。
1)計算公式:
  • 計算公式為:V(G)=e-n+2。其中,e表示控制流圖中邊的數量,n表示控制流圖中節點的數量。 其實,圈復雜度的計算還有更直觀的方法,因為圈復雜度所反映的是“判定條件”的數量,所以圈復雜度實際上就是等於判定節點的數量再加上1,也即控制流圖的區域數,對應的計算公式為:V(G)=區域數=判定節點數+1。
  • 對於多分支的CASE結構或IF-ELSE結構,統計判定節點的個數時需要特別注意一點,要求必須統計全部實際的判定節點數,也即每個 ELSEIF語句,以及每個CASE語句,都應該算為一個判定節點。判定節點在模塊的控制流圖中很容易被識別出來,所以,針對程序的控制流圖計算圈復雜度 V(G)時,最好還是采用第一個公式,也即V(G)=e-n+2;而針對模塊的控制流圖時,可以直接統計判定節點數,這樣更為簡單。

4.如何重構這樣的代碼呢?

1) NULL Object空對象模式

  • 描述: 當你在處理可能會出現null的對象時,可能要產生相對乏味的代碼來做相應的處理,使用空對象模式可以接受null,並返回相應的信息。
  • 代碼示例:
interface ILog { void log(); } class FileLog implements ILog { public void log() { } } class ConsoleLog implements ILog { public void log() { } } class NullObjectLog implements ILog { public void log() { } } public class LogFactory { static ILog Create(String str) { ILog log = new NullObjectLog(); if ("file".equals(str)) log = new FileLog(); if ("console".equals(str)) log = new ConsoleLog(); return log; } }

2) 表驅動法(Table-Driven Approach)

  • 描述:表驅動法是一種設計模式,可用來代替復雜的if、else邏輯判斷。
觀察下面的一維數組的形式可以發現,定義了2個變量。
例如:int a[12],a[x]=y;
相當於函數 y=f(x) (在此例子中,x和y為均為int類型),於是就變成了我們平時熟悉的普通的c函數。而函數一般通過數學表達式和邏輯判斷的形式得出結果,而這樣的結果一般的來說有數學規律,例如像n!就很適合於使用函數實現。
而表驅動法的函數關系是人為定義的,如果采用函數,一般會出現很多的if、else判斷。所以表驅動法適合於去實現“人造邏輯”的函數。
例子1:假設你要編寫一個計算醫療保險費用的程序,其中保險費用是隨着性別、年齡、婚姻狀況 和是否吸煙而變化的。(這是代碼大全書上的例子,原版是Pasca版本,方便閱閱讀改成Java辦)。這時候,第一反應可能就是一大堆的if-else語句,如下:
enum SexStatus { Female, Male } enum MaritalStatus { Single, Married } enum SmokingStatus { NonSmoking, Smoking } public double ComputeInsuranceCharge(SexStatus sexStatus, MaritalStatus maritalStatus, SmokingStatus smokingStatus, int age) { double rate = 1; if (sexStatus.equals(SexStatus.Female)) { if (maritalStatus.equals(MaritalStatus.Single)) { if (smokingStatus.equals(SmokingStatus.NonSmoking)) { if (age < 18) { rate = 40.00; } else if (age == 18) { rate = 42.50; } else if (age == 19) { rate = 45.00; } ... else if (age > 65) { rate = 150.00; } } else if (smokingStatus == SmokingStatus.Smoking) { if (age < 18) { rate = 44.00; } else if (age == 18) { rate = 47.00; } else if (age == 19) { rate = 50.00; } ... else if (age > 65) { rate = 200.00; } } } else if (maritalStatus == MaritalStatus.Married) { //...... } } return rate; }

但是仔細看一下代碼,其實保險費率和性別、婚姻、是否抽煙、年齡這個幾個因素有一定的關系,尤其年齡的變化區間是相當大,按照上述的寫法,可想而知,代碼的復雜會達到什么樣子的程度。
這時候,肯定有人會想不需要對每個年齡進行判斷,而且將保險費用放入年齡數組中,這樣將極大地改進上述的代碼。不過,如果把保險費用放入所有影響因素的數組而不僅僅是年齡數組的話,將會使程序更簡單,類似於可以設計一個費率表格,來降低代碼的復雜度呢。

  • 定義好費率表格之后, 你就需要確定如何把數據放進去。你可以用從文件中讀入費率表格數據。
  • 當你建立好數據之后, 便做好了計算保險費用的一切工作。現在就可以用下面這個簡單的語句來代替前面那個復雜的邏輯結構了。
Table<Integer, RateFactor, Double> rateTable = HashBasedTable.create();
enum RateFactor { MALE_SINGLE_NONSMOKING, MALE_SINGLE_SMOKING, MALE_MARRIED_NONSMOKING, MALE_MARRIED_SMOKING, FEMALE_SINGLE_NONSMOKING, FEMALE_SINGLE_SMOKING, FEMALE_MARRIED_NONSMOKING, FEMALE_MARRIED_SMOKING, } public double ComputeInsuranceCharge(RateFactor rateFactor, int age) { int ageFactor; if (age < 18) { ageFactor = 0; } else if (age > 65) { ageFactor = 65 - 17; } else { ageFactor = age - 17; } return rateTable.get(ageFactor, rateFactor); }
例子2:前端語言可以采用表驅動法簡化邏輯嗎?

原先代碼:

switch (something) { case 1: doX(); break; case 2: doY(); break; case 3: doN(); break; // And so on... } 

重構后代碼:

 var cases = { 1: doX, 2: doY, 3: doN }; if (cases[something]) { cases[something](); } 

關於表驅動法,還是其他靈活的使用方法,可以參考《代碼大全》

3) 繼承子類的多態

使用繼承子類的多態,它們使用對象的間接性有效地擺脫了傳統的狀態判斷。
使用繼承子類多態的方式,通常對於某個具體對象,它的狀態是不可改變的(在對象的生存周期中)。

  • 原先代碼:
public class Method { private int type; public static final int POST = 0; public static final int GET = 1; public static final int PUT = 2; public static final int DELETE = 3; public Method(int type) { this.type = type; } public String getMethod() throws RuntimeException { switch (type) { case POST: return "這是 POST 方法"; case GET: return "這是 GET 方法"; case PUT: return "這是 PUT 方法"; case DELETE: return "這是 DELETE 方法"; default: throw new RuntimeException("方法類型調用出錯"); } } }
  • 重構方法: 現在使用四個子類分別代表四種類型的方法。這樣就可以使用多態將各個方法的具體邏輯分置到子類中去了

4) 使用state模式

如果希望對象在生存周期內,可以變化自己的狀態,則可以選擇state模式。
重構方法:這里抽象狀態為一個接口MethodType,四種不同的狀態實現該接口。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM