一、為什么會(huì)出現(xiàn)大量的if-else代碼
當(dāng)你使用if-else的時(shí)候,無(wú)非是以下的兩種情況:
1.1 異常情況判斷的需要
在很多情況下,我們?cè)趯?duì)一個(gè)對(duì)象進(jìn)行操作之前,需要對(duì)其進(jìn)行非空的判斷,不然很容易出現(xiàn)NPE異常,代碼示例如下:
Fruit fruit = new Fruit();
if(fruit != null){
// 正常的處理邏輯
} else {
// 異常的處理邏輯
}
1.2 不同處理邏輯的需要
還有一些情況,我們需要根據(jù)對(duì)象中某個(gè)屬性的值不同,來(lái)決定走哪條具體的業(yè)務(wù)邏輯,代碼示例如下:
Fruit fruit = new Fruit();
if(fruit == null){
return;
}
if(fruit.getType == 1){
// 處理邏輯1
}else if(fruit.getType == 2) {
// 處理邏輯2
}else{
// 處理邏輯3
}
大量的if-else代碼容易讓邏輯變得異常復(fù)雜,維護(hù)性和可靠性極差。
二、幾種推薦的優(yōu)化方法
幸運(yùn)的是,前輩們已經(jīng)總結(jié)出了很多有效改進(jìn)if-else代碼的方法,一起來(lái)看看哪一款最適合你。
2.1 合并條件表達(dá)式
如果你發(fā)現(xiàn)一段代碼中,有多個(gè)不同的if-else里面處理的邏輯是相同的,那么就意味著這些分支其實(shí)是可以合并的,如下代碼給出了示例:
if(age < 18){
return false;
}
...
if(height < 165){
return false;
}
...
if(weight > 100){
return false;
}
...
return true;
在如上的代碼示例中,我們看到前三個(gè)if里面的邏輯是相同的,那么我們就可以將這三個(gè)條件判斷進(jìn)行合并:
if(age < 18 || height < 165 || weight > 100){
return false;
}
...
return true;
2.2 異常條件先退出
比如下面的代碼示例,我們想要找同事討論一個(gè)問(wèn)題,那么需要首先判斷是不是工作日,是的話還要判斷他是不是請(qǐng)假了沒(méi)來(lái),如果來(lái)的話,還要判斷他是不是正在忙,如果不忙的話,才能找他討論。代碼示例如下:
if(isWorkDay()){
if(isAbsent()){
return false;
}else{
if(isBusy()){
return false;
}else{
return true;
}
}
}else{
return false;
}
這樣一段代碼,是不是很難一下子弄清楚整個(gè)邏輯究竟是想干啥。現(xiàn)在,我們嘗試把所有的異常條件都放在最前面,優(yōu)先退出,然后優(yōu)化后的結(jié)果如下:
if(!isWorkDay()){
return false;
}
if(isAbsent()){
return false;
}
if(isBusy()){
return false;
}
return true;
這時(shí)候,我們發(fā)現(xiàn),這段代碼還可以使用合并條件表達(dá)式再次進(jìn)行優(yōu)化:
if(!isWorkDay() || isAbsent()() || isBusy()){
return false;
}
return true;
如此,代碼邏輯瞬間感覺(jué)清晰多了。
2.3 正常流程外移
如果主流程放在if-else里面,甚至放在好幾層的if-else里面,估計(jì)也很難看清楚什么條件下才會(huì)執(zhí)行它吧,我們先來(lái)看一個(gè)例子:
double cost = 0.0;
if(price > 100){
if(weight > 50){
cost = getAccount() - price * weight;
}
}
return cost;
在這段代碼中,計(jì)算cost的邏輯是主流程,我們應(yīng)該盡量保持其在if代碼塊之外,優(yōu)化后的結(jié)果如下:
if(price <= 100 || weight <= 50){
return 0.0
}
return getAccount() - price * weight;
我們不但將主體代碼外移了,還去除了臨時(shí)變量,如此,就能很清楚地看到什么條件下返回什么結(jié)果。
2.4 邏輯封裝成方法
當(dāng)if-else中有大量的語(yǔ)句代碼的時(shí)候,可以考慮將這些代碼單獨(dú)封裝成一個(gè)方法,然后調(diào)用方法。
Fruit fruit = new Fruit();
double cost = 0.0;
if(fruit == null){
return cost;
}
if(fruit.getType == 1){
// 一大段代碼1
}else if(fruit.getType == 2) {
// 一大段代碼2
}else{
// 一大段代碼3
}
優(yōu)化后:
Fruit fruit = new Fruit();
double cost = 0.0;
if(fruit == null){
return cost;
}
if(fruit.getType == 1){
return getOneCost();
}else if(fruit.getType == 2) {
return getTwoCost();
}else{
return getThreeCost();
}
private double getOneCost(){
// 一大段代碼1
}
private double getTwoCost(){
// 一大段代碼2
}
private double getThreeCost(){
// 一大段代碼3
}
2.5 使用衛(wèi)語(yǔ)句
衛(wèi)語(yǔ)句是為了消除else,將所有的條件分支以平行的方式進(jìn)行展示。
if(isWorkDay()){
...
}
if(isAbsent()){
...
}
if(isBusy()){
...
}
...
三、重構(gòu)的建議
上述優(yōu)化的建議都是比較簡(jiǎn)單的,且對(duì)代碼的改動(dòng)比較少。如果你期待更高層次,更加優(yōu)雅的代碼優(yōu)化,就需要重構(gòu)成如下兩種設(shè)計(jì)模式的形式。
3.1 策略模式
一個(gè)典型的名單創(chuàng)建場(chǎng)景原代碼邏輯如下:
if(customer == null) {
return false;
}
if("TEL".equals(customer.getSource())){
// 執(zhí)行電話來(lái)源的名單創(chuàng)建過(guò)程
} else if("SMS".equals(customer.getSource())){
// 執(zhí)行短信來(lái)源的名單創(chuàng)建過(guò)程
} else if("WECHAT".equals(customer.getSource())){
// 執(zhí)行微信來(lái)源的名單創(chuàng)建過(guò)程
} else {
// 執(zhí)行默認(rèn)來(lái)源的名單創(chuàng)建過(guò)程
}
return true;
我們可以看到,代碼對(duì)不同的名單來(lái)源進(jìn)行了不同的處理過(guò)程,但是隨著名單來(lái)源的增加和每種名單來(lái)源處理邏輯的復(fù)雜化,代碼的可讀性和維護(hù)性就變得很差。
現(xiàn)在我們使用策略模式對(duì)其進(jìn)行優(yōu)化:
public abstract class CustomerCreateStrategy{
public abstract void createCustomer();
// 公共方法,不管那種來(lái)源的名單創(chuàng)建流程都需要調(diào)用
public void recordCustomer(){
// 執(zhí)行記錄名單創(chuàng)建的記錄
}
}
public class TelCustomerCreateStrategy extends CustomerCreateStrategy {
@Override
public void createCustomer(){
// 執(zhí)行電話來(lái)源的名單創(chuàng)建邏輯
}
}
其它三種名單創(chuàng)建代碼省略,都是大同小異的,然后還要?jiǎng)?chuàng)建一個(gè)環(huán)境類(lèi)。
public class customerCreatorContext{
// 需要保持對(duì)策略類(lèi)的引用
private CustomerCreateStrategy customerCreateStrategy;
public customerCreatorContext(CustomerCreateStrategy customerCreateStrategy){
this.customerCreateStrategy = customerCreateStrategy;
}
public void beginCreateCustomer(){
this.customerCreateStrategy.createCustomer();
}
}
最后,我們?cè)谑褂玫臅r(shí)候代碼如下:
CustomerCreatorContext context =
new CustomerCreatorContext(new TelCustomerCreateStrategy());
context.beginCreateCustomer();
要點(diǎn)是,用戶(hù)必須知道有哪些策略,而具體使用哪一種策略,是由用戶(hù)自己決定的。
3.2 狀態(tài)模式
提醒有不同的狀態(tài),在不同狀態(tài)下允許做不同的操作,下面是原來(lái)的處理邏輯:
if(remind.getDays() < 1) {
// 提醒生成時(shí)間小于1天的邏輯
} else if(remind.getDays() < 3) {
// 提醒生成時(shí)間小于3天的邏輯
} else {
// 提醒生成時(shí)間不小于3天的邏輯
}
這樣的代碼和上面策略模式演示的名單創(chuàng)建邏輯有同樣的問(wèn)題,下面我們使用狀態(tài)模式來(lái)對(duì)其進(jìn)行重構(gòu):
public abstract class RemindState{
public abstract void notifyHandler();
// 公共方法
public void recordRemind(){
// 執(zhí)行記錄邏輯
}
}
public class OneDayRemindState extends RemindState {
@Override
public void notifyHandler(){
// 一日內(nèi)提醒通知處理人的邏輯
}
}
其余的狀態(tài)類(lèi)省略,下面我們定義一個(gè)環(huán)境類(lèi)。
public class RemindNotifier{
// 需要保持對(duì)狀態(tài)類(lèi)的引用
private RemindState remindState;
public beginNotifyHandler(){
// 環(huán)境類(lèi)自己維護(hù)狀態(tài)的變更
Integer days = getPassedDays();
if(days < 1){
this.remindState = new OneDayRemindState();
}else if(days < 3){
this.remindState = new ThreeDayRemindState();
}else{
this.remindState = new ManyDayRemindState();
}
remindState.notifyHandler();
}
}
最后,我們使用的時(shí)候是這樣的:
// 每天通過(guò)定時(shí)任務(wù)啟動(dòng)如下邏輯
RemindNotifier rm = new RemindNotifier();
rm.beginNotifyHandler();
3.3 兩種模式的區(qū)別
我們可以看到,策略模式和狀態(tài)模式非常地相近,都是一個(gè)抽象類(lèi)(接口)和多個(gè)具體類(lèi),再加上一個(gè)環(huán)境類(lèi)。但是在使用過(guò)程中還是有以下區(qū)別:
策略模式需要使用者知道總共有哪些策略可以使用,狀態(tài)模式則不需要;
策略模式需要使用者手動(dòng)指定需要使用哪一種策略,狀態(tài)模式則不需要;
策略模式適用于對(duì)象在整個(gè)生命周期內(nèi)沒(méi)有狀態(tài)變化的情況,而狀態(tài)模式則適用于對(duì)象多狀態(tài)之間頻繁變化的情況;
策略模式一旦選定策略開(kāi)始執(zhí)行,中間無(wú)法變更策略,而狀態(tài)模式是可以的;
-
狀態(tài)模式生命周期中變更狀態(tài)可以由環(huán)境類(lèi)決定,也可以由各個(gè)狀態(tài)類(lèi)之間相互改變,比如A狀態(tài)在執(zhí)行完操作后,自動(dòng)將狀態(tài)對(duì)象轉(zhuǎn)換為下一個(gè)狀態(tài)返回給環(huán)境類(lèi),環(huán)境類(lèi)并不知曉狀態(tài)發(fā)生變化,只是繼續(xù)調(diào)用即可。
public class OpenStatus implements Status{ @Override public Status doSomeThing(){ // 此處省略open狀態(tài)的邏輯 // 將狀態(tài)翻轉(zhuǎn) return new CloseStatus(); } }public class CloseStatus implements Status{ @Override public Status doSomeThing(){ // 此處省略close狀態(tài)的邏輯 // 將狀態(tài)翻轉(zhuǎn) return new OpenStatus(); } }public class SwitchContext{ private Status status = new OpenStatus(); public void start(){ for(int i = 0; i < 10; i++){ status = status.doSomeThing(); } } }
全文完。