去掉臃腫的if else代碼塊(策略設計模式)

2020-12-14 Java實用技術

本號主要用於分享企業中常用的技術,更加側重於實用,歡迎關注,便於瀏覽其它更多實用的歷史文章。

開發中我們會經常寫if(...) { } else if(...) {} else if (...) {}等多個邏輯判斷,一般else if 會有好幾個,多個else if 代碼塊會使得整個方法看起來比較臃腫,這篇文章的目的就是通過策略設計模式減少甚至消滅else if

1. 最簡單的計算器實現(示例1)

public class Main { public static void main(String[] args) { // 計算類型:1:加法 2:減法 3:乘法 4:除法 // 計算類型和計算的值都是正常客戶端傳過來的 int calcType = 1; int num1 = 1; int num2 = 3; // 計算器真正的實現 int result = 0; if (calcType == 1) { result = num1 + num2; } else if (calcType == 2) { result = num1 - num2; } else if (calcType == 3) { result = num1 * num2; } else if (calcType == 4) { result = num1 / num2; } System.out.println(result); }}上面代碼實現了加減乘除功能,代碼看起來直接、簡潔,但是該示例代碼有幾個問題

計算類型使用了魔法值(就是寫死的值),程序中應該避免直接使用魔法值,可以使用枚舉類來優化;計算器功能的每種操作非常簡單,只需要對兩個數字運算一下就完了,每個操作也就是一行代碼的事,但實際開發過程中每種業務處理可能要幾十行甚至更多行代碼來完成,現在是4種類型,如果每種類型的業務需要100行代碼,這四種就會有400行代碼,這只是if else的代碼量再加上該方法的其它代碼可以想像該方法的代碼量很大,我們需要控制方法的行數,一般通常控制到100行以內,我們可以通過將每個運算的具體實現都提取成一個單獨的方法,這樣一來就縮小每個if else的代碼的行數,只需要一行代碼,只需要調用一個方法就可以了。2. 枚舉類+提取方法實現(示例2)

增加一個枚舉類CalcTypeEnum用於表示所有的運算類型, 使用枚舉類來優化魔法值將每種具體的運算實現提取為獨立的方法public enum CalcTypeEnum { ADD(1, "加法操作"), SUB(2, "減法操作"), MUL(3, "乘法操作"), DIV(4, "除法操作"), ; private Integer code; private String description; CalcTypeEnum(Integer code, String description) { this.code = code; this.description = description; } public Integer getCode() { return code; } public String getDescription() { return description; }}public class Main { public static void main(String[] args) { // 計算類型:1:加法 2:減法 3:乘法 4:除法 int calcType = 1; int num1 = 1; int num2 = 3; int result = 0; // 使用枚舉來代替魔法值 if (CalcTypeEnum.ADD.getCode().equals(calcType)) { // 假如加法運算中還包含其它業務邏輯,那麼這些邏輯也被封裝到方法中了,此處只有一行的代碼量 result = add(num1, num2); } else if (CalcTypeEnum.SUB.getCode().equals(calcType)) { result = sub(num1, num2); } else if (CalcTypeEnum.MUL.getCode().equals(calcType)) { result = mul(num1, num2); } else if (CalcTypeEnum.DIV.getCode().equals(calcType)) { result = div(num1, num2); } System.out.println(result); } /** 將具體的運算實現代碼封裝成獨立的方法 */ public static int add(int num1, int num2) { System.out.println("加法運算其它業務邏輯start"); System.out.println("此處省略幾十行代碼..."); System.out.println("加法運算其它業務邏輯end"); return num1 + num2; } public static int sub(int num1, int num2) { return num1 - num2; } public static int mul(int num1, int num2) { return num1 * num2; } public static int div(int num1, int num2) { return num1 / num2; }}該示例解決了上個示例的缺點,但該示例仍有問題:

main方法屬於客戶端,加減乘除功能是具體的功能實現,用來提供服務,我們不能將服務和客戶端代碼寫到一起,每個類的職責應該更明確,即服務提供方用於專門提供服務,客戶端調用方用於調用服務。

3. 計算器功能單獨封裝成類(示例3)

我們將具體的功能單獨封裝到一個類中用於提供服務

public class Calculator { public int calc(CalcTypeEnum calcType, int num1, int num2) { int result = 0; if (CalcTypeEnum.ADD.equals(calcType)) { result = add(num1, num2); } else if (CalcTypeEnum.SUB.equals(calcType)) { result = sub(num1, num2); } else if (CalcTypeEnum.MUL.equals(calcType)) { result = mul(num1, num2); } else if (CalcTypeEnum.DIV.equals(calcType)) { result = div(num1, num2); } return result; } public int add(int num1, int num2) { System.out.println("加法運算其它業務邏輯start"); System.out.println("此處省略幾十行代碼..."); System.out.println("加法運算其它業務邏輯end"); return num1 + num2; } public int sub(int num1, int num2) { return num1 - num2; } public int mul(int num1, int num2) { return num1 * num2; } public int div(int num1, int num2) { return num1 / num2; }}因為服務方單獨封裝成一個獨立的類,所以服務調用方代碼就減少了很多,顯得很清爽。

public class Main { public static void main(String[] args) { int num1 = 1; int num2 = 3; Calculator calculator = new Calculator(); int result = calculator.calc(CalcTypeEnum.ADD, num1, num2); System.out.println(result); }}這個示例已經比上一個示例優化的好多了,假如我們需要對計算器增強功能,假如我們再為計算器增加一個求餘數的功能,該如何實現呢?

枚舉類CalcTypeEnum增加乘方的枚舉值修改Calculator類,增加一個乘方的方法,然後在calc方法中再增加一個else if 塊該實現的最大的問題是當我們新增一種運算時除了要新增代碼,還需要修改原來的calc代碼,這不太好,我們希望新增運算功能時只需要新增代碼,不要修改原來已有的代碼。

4.將具體運算分別封裝成單獨的類中(示例4)

為了達到新增運算方式不修改代碼只增加代碼的方式,我們需要將每個預算繼續抽象,我們新增一個計算的接口,並將加減乘除求餘數分別封裝到每個具體的實現類裡面。

/** * 計算策略. * */public interface CalcStrategy { int calc(int num1, int num2);}/** * 加法操作 */public class AddStrategy implements CalcStrategy { @Override public int calc(int num1, int num2) { System.out.println("加法運算其它業務邏輯start"); System.out.println("此處省略幾十行代碼..."); System.out.println("加法運算其它業務邏輯end"); return num1 + num2; }}/** * 減法操作. */public class SubStrategy implements CalcStrategy { @Override public int calc(int num1, int num2) { return num1 - num2; }}/** * 乘法操作 */public class MulStrategy implements CalcStrategy { @Override public int calc(int num1, int num2) { return num1 * num2; }}/** * 除法操作 */public class DivStrategy implements CalcStrategy { @Override public int calc(int num1, int num2) { return num1 / num2; }}我們增加一個求餘數的運算只需要增加一個枚舉值並新增一個求餘的實現類, 這樣我們就實現了新增一個功能只需要新增代碼而不用修改之前的代碼的目的。

public enum CalcTypeEnum { ADD(1, "加法操作"), SUB(2, "減法操作"), MUL(3, "乘法操作"), DIV(4, "除法操作"), // 求餘數運算 REM(5, "求餘操作"), ;}/** * 求餘操作 */public class RemStrategy implements CalcStrategy { @Override public int calc(int num1, int num2) { return num1 % num2; }}public class Main { // 用戶要計算的類型 private static final int CALC_TYPE = 5; public static void main(String[] args) { // 根據用戶要運算的類型調用相應實現類的方法 CalcStrategy calcStrategy = null; if (CalcTypeEnum.ADD.getCode().equals(CALC_TYPE)) { calcStrategy = new AddStrategy(); } else if (CalcTypeEnum.SUB.getCode().equals(CALC_TYPE)) { calcStrategy = new SubStrategy(); } else if (CalcTypeEnum.MUL.getCode().equals(CALC_TYPE)) { calcStrategy = new MulStrategy(); } else if (CalcTypeEnum.DIV.getCode().equals(CALC_TYPE)) { calcStrategy = new DivStrategy(); } else if (CalcTypeEnum.REM.getCode().equals(CALC_TYPE)) { calcStrategy = new RemStrategy(); } int result = calcStrategy.calc(10, 20); System.out.println(result); }}5. 為示例4引入上下文(示例5)

上下文只持有一個運算接口的引用並提供一個執行策略的方法,這裡的方法實現也很簡單,就是簡單調用具體運算實現類的方法

public class CalcStrategyContext { // 運算接口 private CalcStrategy strategy; // 通過構造函數或者set方法賦值 public CalcStrategyContext(CalcStrategy strategy) { this.strategy = strategy; } public int executeStrategy(int a, int b) { // 簡單調用具體實現對應的方法 return strategy.calc(a, b); } public CalcStrategy getStrategy() { return strategy; } public void setStrategy(CalcStrategy strategy) { this.strategy = strategy; }}public class Main { private static final Integer CALC_TYPE = 1; public static void main(String[] args) { CalcStrategy calcStrategy = null; if (CalcTypeEnum.ADD.getCode().equals(CALC_TYPE)) { calcStrategy = new AddStrategy(); } else if (CalcTypeEnum.SUB.getCode().equals(CALC_TYPE)) { calcStrategy = new SubStrategy(); } else if (CalcTypeEnum.MUL.getCode().equals(CALC_TYPE)) { calcStrategy = new MulStrategy(); } else if (CalcTypeEnum.DIV.getCode().equals(CALC_TYPE)) { calcStrategy = new DivStrategy(); } // 這裡不再直接調用接口方法了,而是調用上下文類中的方法 // 上下文就是對接口的一種簡單的裝飾和封裝 CalcStrategyContext context = new CalcStrategyContext(calcStrategy); int result = context.executeStrategy(20, 30); System.out.println(result); }}此示例和上個示例不同的就是調用方法的對象不同,一個是直接調用CalcStrategy#calc接口的方法,一個是調用CalcStrategyContext#executeStrategy上下文中的方法,上下文中的方法也是簡單的調用具體的實現類,在這裡感覺上下文沒太大的意義。

上下文存在的意義?

現在加入我們要實現一個促銷的功能,假如目前支持 滿x元減y元、第x件y折、滿x件y折等多種促銷方式,假如業務規定當滿足多種促銷時取所有促銷的最低價。要想實現這種功能,我們首先找出當前訂單能夠享受的促銷類型,然後分別計算每種促銷類型促銷後的促銷價,然後比較所有促銷類型對應的促銷價,取最低的促銷價格。

上下文中大部分情況下是直接調用調用接口的方法,但是也有一些情況是需要在上下文中處理一些邏輯,處理不同實現的依賴關係。

/** * 促銷上下文 * @author Mengday Zhang * @version 1.0 * @since 2019-08-16 */public class PromotionContext { public BigDecimal executeStrategy() { BigDecimal promotionPrice = null; List<PromotionStrategy> promotionList = ...; for (PromotionStrategy strategy : promotionList) { // 執行下次運算需要將上次運算的結果作為下次運算的參數, // 每次計算的結果需要和傳進來的上次運算結果做比較,取最小值作為新的結果返回 promotionPrice = strategy.executeStrategy(promotionPrice); } return promotionPrice; }}到此為止,策略模式的所有元素都體現出來了,沒錯示例5就是策略模式的具體實現。

策略設計模式的特點

提供一個策略接口提供多個策略接口的實現類提供一個策略上下文

策略設計模式優點

可以自由切換算法(具體實現)避免了多條件的判斷(幹掉了if else)擴展性好可以定義新的算法提供給使用者(增加新功能時只需要增加代碼而不需要修改代碼)策略設計模式缺點

算法類數量增多,每個算法都是一個類,這對於初級程式設計師比較難以接受6. 通過枚舉類撤掉幹掉if else(示例6)

上個示例我們看到仍然有很多if else代碼,我們需要減少甚至消滅這種代碼,消滅if else這裡列舉兩種方式

, 一種是通過配置枚舉類。在枚舉中增加對應的運算實現類,並提供一個根據code來獲取對應的枚舉類的方法, 獲取到枚舉類了就獲取到對應的實現類了。

public enum CalcTypeEnum { // code 一般設置為具體實現類的前綴 ADD("Add", "加法操作", new AddStrategy()), SUB("Sub", "減法操作", new SubStrategy()), MUL("Mul", "乘法操作", new MulStrategy()), DIV("Div", "除法操作", new DivStrategy()), ; private String code; private String description; private CalcStrategy calcStrategy; CalcTypeEnum(String code, String description, CalcStrategy calcStrategy) { this.code = code; this.description = description; this.calcStrategy = calcStrategy; } // 根據code獲取對應的枚舉類型 public static CalcTypeEnum getCalcTypeEnum(String code) { for (CalcTypeEnum calcTypeEnum : CalcTypeEnum.values()) { if (calcTypeEnum.getCode().equals(code)) { return calcTypeEnum; } } return null; } public String getCode() { return code; } public String getDescription() { return description; } public CalcStrategy getCalcStrategy() { return calcStrategy; }}public class Main { private static final String CALC_TYPE = "Sub"; public static void main(String[] args) { // 消除if else,根據code獲取到對應的枚舉類,進而獲取到對應的計算實現類 CalcStrategy addStrategy = CalcTypeEnum.getCalcTypeEnum(CALC_TYPE).getCalcStrategy(); CalcStrategyContext context = new CalcStrategyContext(addStrategy); int result = context.executeStrategy(20, 30); System.out.println(result); }}7. 通過反射徹底幹掉if else(方式二)

我們將每個具體的實現類變成單例模式,這裡通過懶漢模式來實現單例類。

/** * 加法操作 */public class AddStrategy implements CalcStrategy { private static AddStrategy addStrategy; private AddStrategy() { } // 懶漢模式實現單例類 public static AddStrategy getInstance() { if (addStrategy == null) { addStrategy = new AddStrategy(); } return addStrategy; } @Override public int calc(int num1, int num2) { System.out.println("加法運算其它業務邏輯start"); System.out.println("此處省略幾十行代碼..."); System.out.println("加法運算其它業務邏輯end"); return num1 + num2; }}通過反射獲取某個具體的類,然後調用具體類的getInstance方法,從而獲取對應的運算實現類。

public class CalcStrategyUtils { public static CalcStrategy getCalcStrategy(String calcType) { try { // 這裡包名是寫死的,開發時需要將實現類統一放到該包下面,類的命名也是有規則的,以Strategy作為後綴 String path = "com.example.design.strategy.demo7." + calcType.concat("Strategy"); Class<?> clazz = Class.forName(path); CalcStrategy instance = (CalcStrategy) clazz.getDeclaredMethod("getInstance").invoke(null, null); return instance; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Load [" + calcType.concat("Strategy") + "] Error :", e); } }}public class Main { private static final String CALC_TYPE = "Add"; public static void main(String[] args) { // 通過反射獲取到具體實現類型,從而消除if else CalcStrategy addStrategy = CalcStrategyUtils.getCalcStrategy(CALC_TYPE); CalcStrategyContext context = new CalcStrategyContext(addStrategy); int result = context.executeStrategy(20, 30); System.out.println(result); }}

相關焦點

  • C語言,去你的策略模式!
    一旦要通過C語言來實現各種設計模式,必定會在嚴謹地維護類層次上造成非常繁瑣和臃腫的代碼。這是因為C++/java等自帶一套面向對象的工具,而C語言要在代碼設計中,不斷地根據實際情況來創造面向對象的工具。
  • 設計模式之策略模式
    行為型設計模式策略模式一簡介當一個對象有多種行為要執行時,我們可以使用策略模式,讓對象自由選擇執行的行為種類,避免使用if…else if….else if…這種嵌套代碼假設每種行為的代碼段都是上百行代碼,如果都寫在if…else裡面會顯得代碼冗長,不方便維護。同時,如果某種行為要修改代碼的話,必須動到原來的舊代碼,擴展性不好。如果我們使用策略模式,某個行為要修改代碼,我們只要修改對應行為的類,都不用動到舊代碼。後期如果要撤掉某種行為或增加某種行為,都比較方便。
  • 策略模式
    思考一下:實現某一功能可以有多種算法或策略,我需要根據條件選擇相應的算法或策略來完成該功能。如:排序算法,可以使用冒泡排序、歸併排序、快速排序等。要怎麼實現呢?很多同學想,寫個類,每個方法是一種排序算法,然後再封裝一個方法寫 if...else...
  • Java設計模式之策略模式詳解
    前言在軟體領域中,設計模式作為一種經典的開發實踐常常需要我們去深入的理解,而策略模式作為設計模式的一種,使用頻率也是相對來說比較高的,在Java中,當我們學習TreeSet集合的時候,就採用了經典的策略模式的思想,本文主要講解策略模式。
  • if-else代碼優化的八種方案
    優化方案一:提前return,去除不必要的else如果if-else代碼塊包含return語句,可以考慮通過提前return,把多餘else幹掉,使代碼更加優雅。優化方案八:策略模式+工廠方法消除if else假設需求為,根據不同勳章類型,處理相對應的勳章服務,優化前有以下代碼:String medalType = "guest"; if ("guest".equals(medalType)) {
  • 設計模式之策略模式(Java實現例子說明)
    設計模式之策略模式小王在和同事正在吹牛時,領導過來了,小王啊,你又在吹牛了?別吹了,幹點正活,下周要去春遊,你給出幾種方案。小王一聽去旅遊,屁顛屁顛地去幹活了。這個其實就是一個設計模式,叫做策略模式。策略模式(Strategy Pattern):定義一組算法,將每個算法封裝起來,並且使他們之間可以互換。策略模式是一個簡單的模式也叫做政策模式。
  • 如何優化你的if-else?來試試「責任樹模式」
    由於該接口已經開發了三期了,每次開發新一期的需求時為了兼容老的業務邏輯,大家都傾向於不刪不改只新增,因此這塊代碼已經產生了一些「壞味道」,函數入口通過不斷添加「衛語句」判斷 version 的方式跳轉到新一期的業務邏輯方法中,而每一期的業務邏輯也是通過 p1、p2、p3 的 if-else 組合形成不同的分支邏輯。
  • Python if else條件語句詳解
    表 1 if else 分支語句的三種形式語法格式執行流程if 表達式:    代碼塊if 表達式:    代碼塊 1else:    代碼塊 2if 表達式 1:    代碼塊 1elif 表達式 2:    代碼塊 2elif 表達式 3:    代碼塊 3...
  • R語言決策結構,if語句,if..else語句
    在R語言中創建if語句的基本模式if(boolean_expression) {   // statement(s) will
  • Python基礎知識儲備,關於if-else使用性能的一點感悟
    前面給大家介紹的if-if分支結構是只要條件滿足就會執行;而if-else的語句結構是前面的條件成立執行什麼操作,則下面的分支結構就不會被執行,若前面的條件不成立(否則)做什麼。以下通過三個案例給大家演示if-else語句的使用方法:
  • 掃盲:策略模式,成事兒還需要策略
    寫在前面前面也給大家說了,掃盲系列第一個專題是:設計模式,今天它來啦。設計模式是我們開發的內功,很多同學都知道設計模式很有用,但我們卻總是很難記住它,尤其是對於定義。其實,我們從來都不需要去記住它,但學習是必要的,我們需要把每一個招式化為自己的血肉,在編寫代碼的時候多一種設計手段。設計模式來源於生活,我們更應該用生活和它類比起來。
  • 設計模式——策略模式
    設計模式——策略模式1. 簡單工廠實現面向對象的編程,並不是類越多越好,類的劃分是為了封裝,但分類的基礎是抽象,具有相同屬性和功能的對象的抽象集合才是類。2.策略模式策略這個詞應該怎麼理解,打個比方說,我們出門的時候會選擇不同的出行方式,比如騎自行車、坐公交、坐火車、坐飛機、坐火箭等等,這些出行方式,每一種都是一個策略。
  • 程式設計師:運用策略模式,重構 if else 冗餘代碼
    一,介紹先上案例if (msgType = "文本") {// dosomething} else if(msgType = "圖片") {// doshomething} else if(msgType = "視頻") {// doshomething} else {// doshomething}隨著代碼的業務邏輯有時候會伴隨著很多的
  • Python第6課:if elif else 多條件多分支 結構
    if  表達式1:     代碼塊1elif  表達式2:     代碼塊2elif  表達式3:>     代碼塊3…//其他elif語句else :     代碼塊n :  冒號:  冒號:  冒號重要的事必須說三遍if  後面有冒號elif  後面有冒號
  • 程式設計師必學設計模式,策略模式,讓你少改代碼提升效率
    經常有人會說,程式設計師寫代碼要什麼設計模式,隨便寫就行,這其實是一種很不好的習慣,就好比蓋房子,不需要設計圖隨便蓋就可以,但是想要蓋成高樓大廈,一定要經過嚴謹的設計,不然就容易崩塌。我們採用設計模式來寫代碼,更多的,是為了不坑自己。
  • 第五課,Python條件判斷單向判斷:if雙向判斷:if……else多向判斷
    此時,被縮進的所有內容被稱為if語句內的代碼塊。這就表示: 如果滿足if條件,計算機就會執行if語句內的代碼塊。(縮進的所有內容)你看,Python是不是又向你吐槽了。IndentationError: expected an indented block(縮進錯誤)這是由於,當我們把縮進去掉時,print語句就已經不在if語句內了, if語句與print語句已經成為了兩個不同的代碼,平行的兄弟關係。
  • Java中我如何去除if...else...語句?
    提前使用靜態代碼塊把對象存入map容器中,在需要的時候在取。他也有提到可以使用DI的方式把需要的對象提前注入好,但是這兩種方式都會造成內存的浪費,因為有一些對象可能是頻繁使用,而有些對象用的概率小甚至一次都沒有用到,那麼這樣的方式是不好的。而且,我們是去除if…else…的語句,這樣的方式雖然好像沒有了if…else…語句,但是本質上並不是最好的方式,只是提供了一種思維方式。
  • 乾貨,策略模式你不知道的
    百度一搜,策略模式想必各類文章都有,今天講一講你在其他地方看不到的策略模式的思想。代碼誰都會寫,思想不是誰都有的。策略模式上節我們稍微的探討了下SpringSession中包含的設計模式。SpringSession 設計模式分析這節我們應用下SpringSession 所涉及的策略模式。
  • 策略模式(上)
    今年開始深入學習設計模式,並把所思所想記錄下來,以備查閱。因為我以《Head First 設計模式》一書為導向,所以第一個學習的模式是策略模式。本篇為策略模式的上篇,我以傳統的嚴格意義上的面向對象語言 Java為例來說明此模式;我會在下一篇用非嚴格意義上的OO語言 Go基於同樣的例子進行說明。
  • Vue 騷技巧,策略模式實現動態表單驗證
    封裝的策略算法一般是獨立的,策略模式根據輸入來調整採用哪個算法。關鍵是策略的實現和使用分離。:priceCalculate 函數隨著折扣類型的增多,if-else 判斷語句會變得越來越臃腫;如果增加了新的折扣類型或者折扣類型的算法有所改變,那麼需要更改 priceCalculate 函數的實現,這是違反開放-封閉原則的;可復用性差,如果在其他的地方也有類似這樣的算法,但規則不一樣,上述代碼不能復用;我們可以改造一下,將計算折扣的