原文自工程師baeldung部落格,傳送門
可關注我們的收藏夾,最新的翻譯文章都在這裡。
1. 概述
決策結構在大多數程式語言中佔據了至為重要的一步。但是我們常常會被大量的那種讓程式碼變得難讀且難維護的內嵌if語句搞得渾身難受。
在這次的教程中,我們將來過一下可以代替內嵌if語句的各種方法。讓我們來探索簡化我們程式碼的途徑吧。
2. 案例學習
通常我們會遇到一些需要做一系列條件處理的業務邏輯,並且它們每一個都需要不同的處理。
為了演示,我們來看一下Calulator(計算器)類的一個例子。上面是帶有兩個數字型別引數,一個操作符引數以及基於操作的數值返回值的一個方法:
public int calculate(int a, int b, String operator) {
int result = Integer.MIN_VALUE;
if ("add".equals(operator)) {
result = a + b;
} else if ("multiply".equals(operator)) {
result = a * b;
} else if ("divide".equals(operator)) {
result = a / b;
} else if ("subtract".equals(operator)) {
result = a - b;
}
return result;
}
複製程式碼
通常我們也能使用switch語句來操作:
public int calculateUsingSwitch(int a, int b, String operator) {
switch (operator) {
case "add":
result = a + b;
break;
// other cases
}
return result;
}
複製程式碼
在典型的開發過程中,本質上if語句會使程式變得更為臃腫和複雜。並且,switch語句也並非所有場景都適用,當條件複雜的時候,switch語句就沒什麼作用了。
另一個使用巢狀條件宣告程式設計的影響是它使得程式變得難以管理。例如,如果我們需要新新增一個操作,我們需要新增一個新的if條件以及條件的實現。
3. 重構
讓我們嘗試下使用其他更簡潔和易管理的方法來代替這個複雜的if語句吧。
3.1 工廠類
很多時候我們經常遇見許多條件宣告,它們是用來處理每個分支中相似的操作。這提供給我們一個想法,提取一個返回具體型別的物件並且根據具體物件行為執行操作的工廠類。
例如在下面,讓我們定義一個帶有單獨的apply方法的操作介面
public interface Operation {
int apply(int a, int b);
}
複製程式碼
這方法帶有兩個數值型別引數以及數值型別的返回。讓我們來定義一個實現加法的類:
public class Addition implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
複製程式碼
我們現在將要實現一個返回基於給定操作符的操作例項的工程類:
public class OperatorFactory {
static Map<String, Operation> operationMap = new HashMap<>();
static {
operationMap.put("add", new Addition());
operationMap.put("divide", new Division());
// more operators
}
public static Optional<Operation> getOperation(String operator) {
return Optional.ofNullable(operationMap.get(operator));
}
}
複製程式碼
現在在Calculator類中,我們能夠通過查詢工廠來獲取相關的操作並且應用於其中:
public int calculateUsingFactory(int a, int b, String operator) {
Operation targetOperation = OperatorFactory
.getOperation(operator)
.orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
return targetOperation.apply(a, b);
}
複製程式碼
在這個例子裡,我們能看到如何通過工廠類來將邏輯業務責任分發委託給一系列輕耦合物件當中。但是如果只是簡單地將巢狀if語句轉移成工廠類,這明顯是不符合我們的目的的。
作為另外的選擇,我們能夠通過維護能夠被快速查詢的物件倉庫map(對映),正如OperatorFactory#operationMap
,來達成我們的目的。我們也能夠在執行時定義對映物件並且配置它們用於查詢。
3.2 使用列舉
除了對映物件(map)的使用之外,我們也可以使用列舉來標記特定的邏輯業務。在這之後,我們能通過它來代替巢狀if語句或者swtich語句了。作為其他處理,我們也可以使用它們作為物件工廠並且整理用於處理相關的業務邏輯操作。
這會減少巢狀if語句的數量並且將業務責任委託給獨立的列舉變數中。
讓我們來看看怎麼去實現它。首先,我們需要定義一個列舉類:
public enum Operator {
ADD, MULTIPLY, SUBTRACT, DIVIDE
}
複製程式碼
像我們看到這樣,這些值是不同操作符的標籤,並且會運用到之後的計算當中。就像巢狀if語句和switch語句那樣,我們可以將這些值當作選項來使用。但和它們不同的地方,讓我們去設計一種能夠將邏輯委託給列舉本身的替代方法吧。
我們為每一個列舉量都定義了各自的方法並且進行了計算操作,例如:
ADD {
@Override
public int apply(int a, int b) {
return a + b;
}
},
// other operators
public abstract int apply(int a, int b);
複製程式碼
然後在Calculator類中,我們也定義了一個用於執行操作的方法:
public int calculate(int a, int b, Operator operator) {
return operator.apply(a, b);
}
複製程式碼
現在,我們可以通過使用Operator#valueOf()
方法來將字串轉換為操作符來呼叫方法了:
@Test
public void whenCalculateUsingEnumOperator_thenReturnCorrectResult() {
Calculator calculator = new Calculator();
int result = calculator.calculate(3, 4, Operator.valueOf("ADD"));
assertEquals(7, result);
}
複製程式碼
3.3 命令模式(command pattern)
在先前的討論中,我們已經看到使用工廠類來返回指定操作符的對應的業務物件例項了,稍後,業務物件例項將用於之後的Claculator中執行計算操作。
我們也能夠設計一個Calculator#calculate
方法來接收一個可以執行輸入的指令。這是另外一種來代替巢狀if語句的方法。
首先我們定義一個Command介面:
public interface Command {
Integer execute();
}
複製程式碼
然後,讓我們實現其中的一個AddCommand:
public class AddCommand implements Command {
// Instance variables
public AddCommand(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public Integer execute() {
return a + b;
}
}
複製程式碼
最後,讓我們在Calculator類中定義一個用於接收操作和執行操作的新方法:
public int calculate(Command command) {
return command.execute();
}
複製程式碼
通過例項化AddCommand物件並且將他作為引數傳遞到Calculator#calculate
方法當中用以呼叫計算方法:
@Test
public void whenCalculateUsingCommand_thenReturnCorrectResult() {
Calculator calculator = new Calculator();
int result = calculator.calculate(new AddCommand(3, 7));
assertEquals(10, result);
}
複製程式碼
3.4 規則引擎(rule engine)
當我們最終編寫了大量的巢狀if語句時,每一個條件都描述了特定的業務規則,用於評估正確邏輯操作的執行。規則引擎將這些複雜的草從主程式碼中去掉。規則引擎是用於評估規則並且基於輸入返回結果。
讓我們通過設計一個簡單的規則引擎來做下試驗。這個引擎是通過一組規則來處理表示式,並從選中的規則返回結果。首先,我們定義一個規則介面:
public interface Rule {
boolean evaluate(Expression expression);
Result getResult();
}
複製程式碼
接著,我們來實現一個規則引擎:
public class RuleEngine {
private static List<Rule> rules = new ArrayList<>();
static {
rules.add(new AddRule());
}
public Result process(Expression expression) {
Rule rule = rules
.stream()
.filter(r -> r.evaluate(expression))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Expression does not matches any Rule"));
return rule.getResult();
}
}
複製程式碼
這個規則引擎接收一個表示式並且返回Result。現在,我們設計一個帶有兩個數值變數以及一個用於操作的Operator物件的表示式(Expression)類:
public class Expression {
private Integer x;
private Integer y;
private Operator operator;
}
複製程式碼
最後,我們定義一個AddRule類,它只在加操作中使用到:
public class AddRule implements Rule {
@Override
public boolean evaluate(Expression expression) {
boolean evalResult = false;
if (expression.getOperator() == Operator.ADD) {
this.result = expression.getX() + expression.getY();
evalResult = true;
}
return evalResult;
}
}
複製程式碼
現在,我們能夠使用Expression來呼叫RuleEngine了:
@Test
public void whenNumbersGivenToRuleEngine_thenReturnCorrectResult() {
Expression expression = new Expression(5, 5, Operator.ADD);
RuleEngine engine = new RuleEngine();
Result result = engine.process(expression);
assertNotNull(result);
assertEquals(10, result.getValue());
}
複製程式碼
4. 總結
在這次教程中,我們探索了一系列不同的方法來簡化複雜的程式碼。同時我們也學到了這麼去使用有效的設計模式來取代繁雜的巢狀fi宣告語句。
一如既往,讀者們可以在我們的github倉庫中獲取到完整的原始碼。
我們下期見。
5. 譯者總結
想必大家以前或多或少都會被這種無止境的if語句所困擾,這篇文章中作者介紹了4種方法用來取代原先維護成本極高的if結構,希望看了以後對大家之後的結構設計思路有所幫助,避免出現讓別人叫慘的if地獄。
小喇叭
廣州蘆葦科技Java開發團隊
蘆葦科技-廣州專業網際網路軟體服務公司
抓住每一處細節 ,創造每一個美好
關注我們的公眾號,瞭解更多
想和我們一起奮鬥嗎?lagou搜尋“ 蘆葦科技 ”或者投放簡歷到 server@talkmoney.cn 加入我們吧
關注我們,你的評論和點贊對我們最大的支援