前言:
最近因為工作中業務需要,代碼里用了大量的if else嵌套。想着如何優化,剛好在網上看到一篇文章,個人覺得寫的還不錯。這邊轉載過來以后后續學習。根據個人理解和需要,自己做了一點修改整理。
作者:leowudev 原文:http://www.apkbus.com/blog-970703-78964.html(已征得原文作者同意,后續轉載需注明來源!!!)
寫在前面:
不知大家有沒遇到過像“橫放着的金字塔”一樣的if else
嵌套:
1 if (true) { 2 if (true) { 3 if (true) { 4 if (true) { 5 if (true) { 6 if (true) { 7 8 } 9 } 10 } 11 } 12 } 13 }
我並沒誇大其詞,我是真的遇到過了!嵌套6、7層,一個函數幾百行,簡!直!看!死!人!
if else
作為每種編程語言都不可或缺的條件語句,我們在編程時會大量的用到。但if else
一般不建議嵌套超過三層,如果一段代碼存在過多的if else
嵌套,代碼的可讀性就會急速下降,后期維護難度也大大提高。所以,我們程序員都應該盡量避免過多的if else
嵌套。下面將會談談我在工作中如何減少if else
嵌套的。
正文:
在談我的方法之前,不妨先用個例子來說明if else
嵌套過多的弊端。
想象下一個簡單分享的業務需求:支持分享鏈接、圖片、文本和圖文,分享結果回調給用戶(為了不跑題,這里簡略了業務,實際復雜得多)。當接手到這么一個業務時,是不是覺得很簡單,稍動下腦就可以動手了:
先定義分享的類型、分享Bean和分享回調類:
1 private static final int TYPE_LINK = 0; 2 private static final int TYPE_IMAGE = 1; 3 private static final int TYPE_TEXT = 2; 4 private static final int TYPE_IMAGE_TEXT = 3; 5 6 public class ShareItem { 7 int type; 8 String title; 9 String content; 10 String imagePath; 11 String link; 12 } 13 14 public interface ShareListener { 15 16 int STATE_SUCC = 0; 17 int STATE_FAIL = 1; 18 19 void onCallback(int state, String msg); 20 }
好了,然后在定義個分享接口,對每種類型分別進行分享就ok了:
1 public void share (ShareItem item, ShareListener listener) { 2 if (item != null) { 3 if (item.type == TYPE_LINK) { 4 // 分享鏈接 5 if (!TextUtils.isEmpty(item.link) && !TextUtils.isEmpty(item.title)) { 6 doShareLink(item.link, item.title, item.content, listener); 7 } else { 8 if (listener != null) { 9 listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整"); 10 } 11 } 12 } else if (item.type == TYPE_IMAGE) { 13 // 分享圖片 14 if (!TextUtils.isEmpty(item.imagePath)) { 15 doShareImage(item.imagePath, listener); 16 } else { 17 if (listener != null) { 18 listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整"); 19 } 20 } 21 } else if (item.type == TYPE_TEXT) { 22 // 分享文本 23 if (!TextUtils.isEmpty(item.content)) { 24 doShareText(item.content, listener); 25 } else { 26 if (listener != null) { 27 listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整"); 28 } 29 } 30 } else if (item.type == TYPE_IMAGE_TEXT) { 31 // 分享圖文 32 if (!TextUtils.isEmpty(item.imagePath) && !TextUtils.isEmpty(item.content)) { 33 doShareImageAndText(item.imagePath, item.content, listener); 34 } else { 35 if (listener != null) { 36 listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整"); 37 } 38 } 39 } else { 40 if (listener != null) { 41 listener.onCallback(ShareListener.STATE_FAIL, "不支持的分享類型"); 42 } 43 } 44 } else { 45 if (listener != null) { 46 listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能為 null"); 47 } 48 } 49 }
到此,簡單的分享模型就做出來了。有沒問題?老實說,如果沒什么追求的話,還真沒什么問題,至少思路是清晰的。但一周后呢?一個月后呢?或者一年后呢?share
方法的分支有15條,這意味着你每次回看代碼得讓自己的大腦變成微型的處理器,考慮15種情況。如果出現bug,你又得考慮15種情況,並15種情況都要測試下。再如果現在需要加多分享小視頻功能,你又得添加多3個分支,還要改代碼,一點都不“開放-閉合”。再再如果后面項目交接給他人跟進,他人又要把自己大腦變成處理器來想每個分支的作用,我敢肯定有百分之八十的人都會吐槽代碼。
我們程序員的腦力不應該花費在無止境的分支語句里的,應該專注於業務本身。所以我們很有必要避免寫出多分支嵌套的語句。好的,我們來分析下上面的代碼多分支的原因:
-
空值判斷
-
業務判斷
-
狀態判斷
幾乎所有的業務都離不開這幾個判斷,從而導致if else
嵌套過多。那是不是沒辦法解決了?答案肯定不是的。
上面的代碼我是用java寫的,對於java程序員來說,空值判斷簡直使人很沮喪,讓人身心疲憊。上面的代碼每次回調都要判斷一次listener
是否為空,又要判斷用戶傳入的ShareItem
是否為空,還要判斷ShareItem
里面的字段是否為空……
對於這種情況,我采用的方法很簡單:接口分層。
減少 if else 方法一:接口分層
所謂接口分層指的是:把接口分為外部和內部接口,所有空值判斷放在外部接口完成,只處理一次;而內部接口傳入的變量由外部接口保證不為空,從而減少空值判斷。
來,看代碼更加直觀:
1 public void share(ShareItem item, ShareListener listener) { 2 if (item == null) { 3 if (listener != null) { 4 listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能為 null"); 5 } 6 return; 7 } 8 9 if (listener == null) { 10 listener = new ShareListener() { 11 @Override 12 public void onCallback(int state, String msg) { 13 Log.i("DEBUG", "ShareListener is null"); 14 } 15 }; 16 } 17 18 shareImpl(item, listener); 19 } 20 21 private void shareImpl (ShareItem item, ShareListener listener) { 22 if (item.type == TYPE_LINK) { 23 // 分享鏈接 24 if (!TextUtils.isEmpty(item.link) && !TextUtils.isEmpty(item.title)) { 25 doShareLink(item.link, item.title, item.content, listener); 26 } else { 27 listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整"); 28 } 29 } else if (item.type == TYPE_IMAGE) { 30 // 分享圖片 31 if (!TextUtils.isEmpty(item.imagePath)) { 32 doShareImage(item.imagePath, listener); 33 } else { 34 listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整"); 35 } 36 } else if (item.type == TYPE_TEXT) { 37 // 分享文本 38 if (!TextUtils.isEmpty(item.content)) { 39 doShareText(item.content, listener); 40 } else { 41 listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整"); 42 } 43 } else if (item.type == TYPE_IMAGE_TEXT) { 44 // 分享圖文 45 if (!TextUtils.isEmpty(item.imagePath) && !TextUtils.isEmpty(item.content)) { 46 doShareImageAndText(item.imagePath, item.content, listener); 47 } else { 48 listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整"); 49 } 50 } else { 51 listener.onCallback(ShareListener.STATE_FAIL, "不支持的分享類型"); 52 } 53 }
可以看到,上面的代碼分為外部接口share
和內部接口shareImpl
,ShareItem
和ShareListener
的判斷都放在share
里完成,那么shareImpl
就減少了if else
的嵌套了,相當於把if else
分攤了。這樣一來,代碼的可讀性好很多,嵌套也不超過3層了。
但可以看到,shareImpl
里還是包含分享類型的判斷,也即業務判斷,我們都清楚產品經理的腦洞有多大了,分享的類型隨時會改變或添加。嗯說到這里相信大家都想到用多態了。多態不但能應付業務改變的情況,也可以用來減少if else
的嵌套。
減少 if else 方法二:多態
利用多態,每種業務單獨處理,在接口不再做任何業務判斷。把ShareItem
抽象出來,作為基礎類,然后針對每種業務各自實現其子類:
1 public abstract class ShareItem { 2 int type; 3 4 public ShareItem(int type) { 5 this.type = type; 6 } 7 8 public abstract void doShare(ShareListener listener); 9 } 10 11 public class Link extends ShareItem { 12 String title; 13 String content; 14 String link; 15 16 public Link(String link, String title, String content) { 17 super(TYPE_LINK); 18 this.link = !TextUtils.isEmpty(link) ? link : "default"; 19 this.title = !TextUtils.isEmpty(title) ? title : "default"; 20 this.content = !TextUtils.isEmpty(content) ? content : "default"; 21 } 22 23 @Override 24 public void doShare(ShareListener listener) { 25 // do share 26 } 27 } 28 29 public class Image extends ShareItem { 30 String imagePath; 31 32 public Image(String imagePath) { 33 super(TYPE_IMAGE); 34 this.imagePath = !TextUtils.isEmpty(imagePath) ? imagePath : "default"; 35 } 36 37 @Override 38 public void doShare(ShareListener listener) { 39 // do share 40 } 41 } 42 43 public class Text extends ShareItem { 44 String content; 45 46 public Text(String content) { 47 super(TYPE_TEXT); 48 this.content = !TextUtils.isEmpty(content) ? content : "default"; 49 } 50 51 @Override 52 public void doShare(ShareListener listener) { 53 // do share 54 } 55 } 56 57 public class ImageText extends ShareItem { 58 String content; 59 String imagePath; 60 61 public ImageText(String imagePath, String content) { 62 super(TYPE_IMAGE_TEXT); 63 this.imagePath = !TextUtils.isEmpty(imagePath) ? imagePath : "default"; 64 this.content = !TextUtils.isEmpty(content) ? content : "default"; 65 } 66 67 @Override 68 public void doShare(ShareListener listener) { 69 // do share 70 } 71 }
(注意:上面每個子類的構造方法還對每個字段做了空值處理,為空的話,賦值default
,這樣如果用戶傳了空值,在調試就會發現問題。)
實現了多態后,分享接口的就簡潔多了:
public void share(ShareItem item, ShareListener listener) { if (item == null) { if (listener != null) { listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能為 null"); } return; } if (listener == null) { listener = new ShareListener() { @Override public void onCallback(int state, String msg) { Log.i("DEBUG", "ShareListener is null"); } }; } shareImpl(item, listener); } private void shareImpl (ShareItem item, ShareListener listener) { item.doShare(listener); }
嘻嘻,怎樣,內部接口一個if else
都沒了,是不是很酷~ 如果這個分享功能是自己App里面的功能,不是第三方SDK,到這里已經沒問題了。但如果是第三方分享SDK的功能的話,這樣暴露給用戶的類增加了很多(各ShareItem
的子類,相當於把if else
拋給用戶了),用戶的接入成本提高,違背了“迪米特原則”了。
處理這種情況也很簡單,再次封裝一層即可。把ShareItem
的子類的訪問權限降低,在暴露給用戶的主類里定義幾個方法,在內部幫助用戶創建具體的分享類型,這樣用戶就無需知道具體的類了:
1 public ShareItem createLinkShareItem(String link, String title, String content) { 2 return new Link(link, title, content); 3 } 4 5 public ShareItem createImageShareItem(String ImagePath) { 6 return new Image(ImagePath); 7 } 8 9 public ShareItem createTextShareItem(String content) { 10 return new Text(content); 11 } 12 13 public ShareItem createImageTextShareItem(String ImagePath, String content) { 14 return new ImageText(ImagePath, content); 15 }
或者有人會說,這樣用戶也需額外了解多幾個方法。我個人覺得讓用戶了解多幾個方法好過了解多幾個類,而已方法名一看就能知道意圖,成本還是挺小,是可以接受的。
其實這種情況,更多人想到的是使用工廠模式。嗯,工廠模式能解決這個問題(其實也需要用戶額外了解多幾個type
類型),但工廠模式難免又引入分支,我們可以用Map
消除分支。
以上,由於轉載的代碼結構不是很清晰,為了后續學習的時候能更清楚理解,我自己把這部分代碼整理了一下。
目錄結構:
分享的類型:
回調類:
定義一個ShareItem的抽象類:
具體的實現類:
減少 if else 方法三:使用Map替代分支語句
把所有分享類型預先緩存在Map
里,那么就可以直接get
獲取具體類型,消除分支:
1 private Map<Integer, Class<? extends ShareItem>> map = new HashMap<>(); 2 3 private void init() { 4 map.put(TYPE_LINK, Link.class); 5 map.put(TYPE_IMAGE, Image.class); 6 map.put(TYPE_TEXT, Text.class); 7 map.put(TYPE_IMAGE_TEXT, ImageText.class); 8 } 9 10 public ShareItem createShareItem(int type) { 11 try { 12 Class<? extends ShareItem> shareItemClass = map.get(type); 13 return shareItemClass.newInstance(); 14 } catch (Exception e) { 15 return new DefaultShareItem(); // 返回默認實現,不要返回null 16 } 17 }
這種方式跟上面分為幾個方法的方式各有利弊,看大家取舍了~