摘要:源碼剖析之設計模式鑒賞策略模式小結在這篇文章中筆者和大家分享幾個減少的小由于這些都會有一定的限制因此還向大家介紹了幾個能夠避免寫出糟糕的的設計模式并使用觀察者模式簡單的改進了仲裁者模式的例子
本文首發于數據浮云:https://mp.weixin.qq.com/s?__...
在寫代碼的日常中,if...else語句是極為常見的.正因其常見性,很多同學在寫代碼的時候并不會去思考其在目前代碼中的用法是否妥當.而隨著項目的日漸發展,糟糕的if...else語句將會充斥在各處,讓項目的可維護性急劇下降.故在這篇文章中,筆者想和大家談談如何避免寫出糟糕if...else語句.
由于脫密等原因.文章中的示例代碼將會用一些開源軟件的代碼或者抽象過的生產代碼作為示范.問題代碼
當我們看到一組if...else時,一般是不會有什么閱讀負擔的.但當我們看到這樣的代碼時:
private void validate(APICreateSchedulerMessage msg) { if (msg.getType().equals("simple")) { if (msg.getInterval() == null) { if (msg.getRepeatCount() != null) { if (msg.getRepeatCount() != 1) { throw new ApiMessageInterceptionException(argerr("interval must be set when use simple scheduler when repeat more than once")); } } else { throw new ApiMessageInterceptionException(argerr("interval must be set when use simple scheduler when repeat forever")); } } else if (msg.getInterval() != null) { if (msg.getRepeatCount() != null) { if (msg.getInterval() <= 0) { throw new ApiMessageInterceptionException(argerr("interval must be positive integer")); } else if ((long) msg.getInterval() * (long) msg.getRepeatCount() * 1000L + msg.getStartTime() < 0 ) { throw new ApiMessageInterceptionException(argerr("duration time out of range")); } else if ((long) msg.getInterval() * (long) msg.getRepeatCount() * 1000L + msg.getStartTime() > 2147454847000L) { throw new ApiMessageInterceptionException(argerr("stopTime out of mysql timestamp range")); } } } if (msg.getStartTime() == null) { throw new ApiMessageInterceptionException(argerr("startTime must be set when use simple scheduler")); } else if (msg.getStartTime() != null && msg.getStartTime() < 0) { throw new ApiMessageInterceptionException(argerr("startTime must be positive integer or 0")); } else if (msg.getStartTime() != null && msg.getStartTime() > 2147454847 ){ // mysql timestamp range is "1970-01-01 00:00:01" UTC to "2038-01-19 03:14:07" UTC. // we accept 0 as startDate means start from current time throw new ApiMessageInterceptionException(argerr("startTime out of range")); } if (msg.getRepeatCount() != null && msg.getRepeatCount() <= 0) { throw new ApiMessageInterceptionException(argerr("repeatCount must be positive integer")); } } if (msg.getType().equals("cron")) { if (msg.getCron() == null || ( msg.getCron() != null && msg.getCron().isEmpty())) { throw new ApiMessageInterceptionException(argerr("cron must be set when use cron scheduler")); } if ( (! msg.getCron().contains("?")) || msg.getCron().split(" ").length != 6) { throw new ApiMessageInterceptionException(argerr("cron task must follow format like this : "0 0/3 17-23 * * ?" ")); } if (msg.getInterval() != null || msg.getRepeatCount() != null || msg.getStartTime() != null) { throw new ApiMessageInterceptionException(argerr("cron scheduler only need to specify cron task")); } } }
亦或是這樣的代碼:
try { for (int j = myConfig.getContentStartNum(); j <= rowNum; j++) { row = sheet.getRow(j); T obj = target.newInstance(); for (int i = 0; i < colNum; i++) { Field colField = ExcelUtil.getOneByTitle(metaList, titleList[i]); colField.setAccessible(true); String fieldType = colField.getType().getSimpleName(); HSSFCell cell = row.getCell(i); int cellType = cell.getCellType(); System.out.println(colField.getName()+"|"+fieldType+" | "+cellType); if(HSSFCell.CELL_TYPE_STRING == cellType){ if("Date".equals(fieldType)){ colField.set(obj, DateUtil.parse(cell.getStringCellValue())); }else { colField.set(obj, cell.getStringCellValue()); } }else if(HSSFCell.CELL_TYPE_BLANK == cellType){ System.out.println("fieldName"+colField.getName()); if("Boolean".equals(fieldType)){ colField.set(obj, cell.getBooleanCellValue()); }else{ colField.set(obj, ""); } }else if(HSSFCell.CELL_TYPE_NUMERIC == cellType){ if("Integer".equals(fieldType) || "int".equals(fieldType)){ colField.set(obj, (int)cell.getNumericCellValue()); }else { colField.set(obj, cell.getNumericCellValue()); } }else if(HSSFCell.CELL_TYPE_BOOLEAN == cellType){ colField.set(obj, cell.getBooleanCellValue()); } } result.add(obj); } } catch (InstantiationException | IllegalAccessException | ParseException e) { e.printStackTrace(); }
看完這兩段代碼,相信大家和我的心情是一樣的:
閱讀它們的負擔實在是太大了——我們要記住好幾個邏輯判斷分支,才能知道到底什么情況下才能得到那個結果.更別說維護的成本有多高了,每次維護時都要讀一遍,然后再基于此來改.長此以往,我們的代碼就變成"箭頭式代碼"了.
//............... //............... //............... //............... //............... //............... //............... //............... //............... //...............目標和關鍵指標
前面說過,我們的目標是減少糟糕的if...else代碼.那么什么是糟糕的if...else代碼呢?我們可以簡單的總結一下:
兩重以上的嵌套
一個邏輯分支的判斷條件有多個,如:A && B || C這種.其實這也可以看作變種的嵌套
這樣就可以看出來,我們的關鍵指標就是減少嵌套.
常見Tips 1. 三元表達式三元表達式在代碼中也是較為常見的,它可以簡化一些if...else,如:
public Object getFromOpaque(String key) { return opaque == null ? null : opaque.get(key); }
為什么說是一些呢?因此三元表達式必須要有一個返回值.
這種情況下就沒法使用三元表達式
public void putToOpaque(String key, Object value) { if (opaque == null) { opaque = new LinkedHashMap(); } opaque.put(key, value); }2. switch case
在Java中,switch可以關注一個變量( byte short int 或者 char,從Java7開始支持String),然后在每個case中比對是否匹配,是的話則進入這個分支.
在通常情況下,switch case的可讀性比起if...else會好一點.因為if中可以放復雜的表達式,而switch則不行.話雖如此,嵌套起來還是會很惡心.
因此,如果僅僅是對 byte,short,int和char以String簡單的值判斷,可以考慮優先使用switch.
3. 及時回頭/* 查找年齡大于18歲且為男性的學生列表 */ public ArrayListgetStudents(int uid){ ArrayList result = new ArrayList (); Student stu = getStudentByUid(uid); if (stu != null) { Teacher teacher = stu.getTeacher(); if(teacher != null){ ArrayList students = teacher.getStudents(); if(students != null){ for(Student student : students){ if(student.getAge() > = 18 && student.getGender() == MALE){ result.add(student); } } }else { throw new MyException("獲取學生列表失敗"); } }else { throw new MyException("獲取老師信息失敗"); } } else { throw new MyException("獲取學生信息失敗"); } return result; }
針對這種情況,我們應該及時拋出異常(或者說return),保證正常流程在外層,如:
/* 查找年齡大于18歲且為男性的學生列表 */ public ArrayList使用設計模式getStudents(int uid){ ArrayList result = new ArrayList (); Student stu = getStudentByUid(uid); if (stu == null) { throw new MyException("獲取學生信息失敗"); } Teacher teacher = stu.getTeacher(); if(teacher == null){ throw new MyException("獲取老師信息失敗"); } ArrayList students = teacher.getStudents(); if(students == null){ throw new MyException("獲取學生列表失敗"); } for(Student student : students){ if(student.getAge() > 18 && student.getGender() == MALE){ result.add(student); } } return result; }
除了上面的幾個tips,我們還可以通過設計模式來避免寫出糟糕的if...else語句.在這一節,我們將會提到下面幾個設計模式:
State模式
Mediator模式
Observer模式
Strategy模式
1. State模式在代碼中,我們經常會判斷一些業務對象的狀態來決定在當前的調用下它該怎么做.我們舉個例子,現在我們有一個銀行的接口:
public interface Bank { /** * 銀行上鎖 * */ void lock(); /** * 銀行解鎖 * */ void unlock(); /** * 報警 * */ void doAlarm(); }
讓我們來看一下它的實現類
public class BankImpl implements Bank { @Override public void lock() { //保存這條記錄 } @Override public void unlock() { if ((BankState.Day == getCurrentState())) { //白天解鎖正常 //僅僅保存這條記錄 } else if (BankState.Night == getCurrentState()) { //晚上解鎖,可能有問題 //保存這條記錄,并報警 doAlarm(); } } @Override public void doAlarm() { if ((BankState.Day == getCurrentState())) { //白天報警,聯系當地警方,并保留這條記錄 } else if (BankState.Night == getCurrentState()) { //晚上報警,可能有事故,不僅聯系當地警方,還需要協調附近的安保人員,并保留這條記錄 } } private BankState getCurrentState() { return BankState.Day; } }
顯然,我們涉及到了一個狀態:
public enum BankState { Day, Night }
在不同的狀態下,同一件事銀行可能會作出不同的反應.這樣顯然很挫,因為在真實業務場景下,業務的狀態可能不僅僅只有兩種.每多一種,就要多寫一個if...else.所以,如果按照狀態模式,可以這樣來重構:
public class BankDayImpl implements Bank { @Override public void lock() { //保存這條記錄 } @Override public void unlock() { //白天解鎖正常 //僅僅保存這條記錄 } @Override public void doAlarm() { //白天報警,聯系當地警方,并保留這條記錄 } }
public class BankNightImpl implements Bank { @Override public void lock() { //保存這條記錄 } @Override public void unlock() { //晚上解鎖,可能有問題 //保存這條記錄,并報警 doAlarm(); } @Override public void doAlarm() { //晚上報警,可能有事故,不僅聯系當地警方,還需要協調附近的安保人員,并保留這條記錄 } }2. Mediator模式
在本文的第一段的代碼中,其實是ZStack 2.0.5版本中某處的代碼,它用來防止用戶使用Cli時傳入不當的參數,導致后面的邏輯運行不正常.為了方便理解,我們可以對其規則做一個簡化,并畫成圖的樣子來供大家理解.
假設這是一個提交定時重啟VM計劃任務的“上古級”界面(因為好的交互設計師一定不會把界面設計成這樣吧...).規則大概如下:
2.1 Simple類型的SchedulerSimple類型的Scheduler,可以根據Interval,RepeatCount,StartTime來定制一個任務.
2.1.1 當選擇Simple類型的任務時,Interval,StartTime這兩個參數必填 2.1.2 當填好Interval,和StartTime,這個時候已經可以提交定時任務了 2.1.3 RepeatCount是個可選參數 2.2 Cron類型的SchedulerCron類型的Scheduler,可以根據cron表達式來提交任務.
在這里請大家思考一個問題,如果要寫這樣的一個界面,該怎么寫?——在一個windows類里,先判斷上面的可選欄是哪種類型,然后根據文本框里的值是否被填好決定提交按鈕屬否亮起...這算是基本邏輯.上面還沒有提到邊界值的校驗——這些邊界值的校驗往往會散落在各個組件的實例里,并通過互相通信的方式來判斷自己應該做出什么樣的變化,相信大家已經意識到了直接無腦堆if...else代碼的恐怖之處了吧.
2.3 使用仲裁者改善它接下來,我們將會貼上來一些偽代碼,方便讀者更好的理解這個設計模式
/** * 仲裁者的成員接口 * */ public interface Colleague { /** * 設置成員的仲裁者 * */ void setMediator(Mediator mediator); /** * 設置成員是否被啟用 * */ void setColleagueEnabled(boolean enabled); }
/** * 仲裁者接口 * */ public interface Mediator { /** * 當一個組員發生狀態變化時,調用此方法 * */ void colllectValueChanged(String value); }
/** * 含有textField的組件應當實現接口 */ public interface TextField { String getText(); }
/** * 當一個組件的值發生變化時,ValueListener會收到相應通知 * */ public interface ValueListener { /** * 當組員的值變化時,這個接口會被調用 * */ void valueChanged(String str); }
定義了幾個接口之后,我們開始編寫具體的類:
用于表示Simple和Cron的checkBox
public class CheckBox { private boolean state; public boolean isState() { return state; } public void setState(boolean state) { this.state = state; } }
Button
public class ColleagueButtonField implements Colleague, ValueListener { private Mediator mediator; @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override public void setColleagueEnabled(boolean enabled) { setEnable(enabled); } private void setEnable(boolean enable) { //當true時去掉下劃線,并允許被按下 } @Override public void valueChanged(String str) { mediator.colllectValueChanged(str); } }
以及幾個Text
public class ColleagueTextField implements Colleague, ValueListener, TextField { private Mediator mediator; private String text; @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override public void setColleagueEnabled(boolean enabled) { setEnable(enabled); } private void setEnable(boolean enable) { //當true時去掉下劃線,并允許值輸入 } @Override public void valueChanged(String str) { mediator.colllectValueChanged(str); } @Override public String getText() { return text; } }
SchedulerValidator的具體實現SchedulerValidatorImpl就不貼上來了,里面僅僅是一些校驗邏輯.
接著是我們的主類,也就是知道全局狀態的窗口類
public class MainWindows implements Mediator { private SchedulerValidator validator = new SchedulerValidatorImpl(); ColleagueButtonField submitButton, cancelButton; ColleagueTextField intervalText, repeatCountText, startTimeText, cronText; CheckBox simpleCheckBox, cronCheckBox; public void main() { createColleagues(); } /** * 當一個組員發生狀態變化時,調用此方法 * 組件初始化時都為true */ @Override public void colllectValueChanged(String str) { if (simpleCheckBox.isState()) { cronText.setColleagueEnabled(false); simpleChanged(); } else if (cronCheckBox.isState()) { intervalText.setColleagueEnabled(false); repeatCountText.setColleagueEnabled(false); startTimeText.setColleagueEnabled(false); cronChanged(); } else { submitButton.setColleagueEnabled(false); intervalText.setColleagueEnabled(false); repeatCountText.setColleagueEnabled(false); startTimeText.setColleagueEnabled(false); cronText.setColleagueEnabled(false); } } private void cronChanged() { if (!validator.validateCronExpress(cronText.getText())) { submitButton.setColleagueEnabled(false); } } private void simpleChanged() { if (!validator.validateIntervalBoundary(intervalText.getText()) || !validator.validateRepeatCountBoundary(repeatCountText.getText()) || !validator.validateStartTime(startTimeText.getText())) { submitButton.setColleagueEnabled(false); } } private void createColleagues() { submitButton = new ColleagueButtonField(); submitButton.setMediator(this); cancelButton = new ColleagueButtonField(); cancelButton.setMediator(this); intervalText = new ColleagueTextField(); intervalText.setMediator(this); repeatCountText = new ColleagueTextField(); repeatCountText.setMediator(this); startTimeText = new ColleagueTextField(); startTimeText.setMediator(this); cronText = new ColleagueTextField(); cronText.setMediator(this); simpleCheckBox = new CheckBox(); cronCheckBox = new CheckBox(); } }
在這個設計模式中,所有實例狀態的判斷全部都交給了仲裁者這個實例來判斷,而不是互相去通信.在目前的場景來看,其實涉及的實例還不是特別多,但在一個復雜的系統中,涉及的實例將會變得非常多.假設現在有A,B兩個實例,那么會有兩條通信線路:
而有A,B,C時,則有6條線路
當有4個實例時,將會有12個通信線路
當有5個實例時,會有20個通信線路
以此類推...
這個時候,仲裁者模式的優點就發揮出來了——這些邏輯如果分散在各個角色中,代碼將會變得難以維護.
3. Observer模式ZStack源碼剖析之設計模式鑒賞——三駕馬車
結合本文的主題,其實觀察者模式做的更多的是將if...else拆分到屬于其自己的模塊中.以ZStack的為例,當主存儲重連時,主存儲模塊可能要讓模塊A和模塊B去做一些事,如果不使用觀察者模式,那么代碼就會都耦合在主存儲模塊下,拆開if...else也就不太可能了.
改進之前的仲裁者例子觀察者模式一般是通過事件驅動的方式來通信的,因此Observer和Subject一般都是松耦合的——Subject發出通知時并不會指定消費者.而在之前仲裁者模式的例子中,仲裁者和成員之間緊耦合的(即他們必須互相感知),因此可以考慮通過觀察者模式來改進它.
4. Strategy模式通常在編程時,算法(策略)會被寫在具體方法中,這樣會導致具體方法中充斥著條件判斷語句。但是Strategy卻特意將算法與其他部分剝離開來,僅僅定義了接口,然后再以委托的方式來使用算法。然而這種做法正是讓程序更加的松耦合(因為使用委托可以方便的整體替換算法),使得整個項目更加茁壯。
ZStack源碼剖析之設計模式鑒賞——策略模式小結
在這篇文章中,筆者和大家分享幾個減少if...else的小tips,由于這些tips都會有一定的限制,因此還向大家介紹了幾個能夠避免寫出糟糕的if...else的設計模式,并使用觀察者模式簡單的改進了仲裁者模式的例子.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/73065.html
摘要:系列文章工匠善用變量改善代碼質量序言編寫條件分支代碼是編碼過程中不可或缺的一部分。而進行條件分支判斷時用到的也是這個值重點來了,雖然所有用戶類實例的布爾值都是真。 歡迎大家前往騰訊云+社區,獲取更多騰訊海量技術實踐干貨哦~ 本文由鵝廠優文發表于云+社區專欄 作者:朱雷 | 騰訊IEG高級工程師 『Python 工匠』是什么? 我一直覺得編程某種意義是一門『手藝』,因為優雅而高效的代碼...
摘要:天生缺乏邏輯性的問題導致了預處理器的出現。這會導致圈復雜度問題。圈復雜度對于來說可能是一種比較高階的原則,但如果我們通過它來考量那些蘊含在我們寫的選擇器中的邏輯性,那我們也許就能寫出更加優秀的代碼。 本文在征得原作者 @csswizardry 同意的情況下,翻譯自他博客中的文章:Cyclomatic Complexity: Logic in CSS。最初發布于我的個人博客:咀嚼之...
摘要:看完代碼整潔之道之后我受益匪淺,但等到自己實踐時卻很難按照書中給的建議編寫出整潔的代碼。意味著新人除了了解代碼邏輯之外,還需要學習這種編碼語言。代碼在演化,注釋卻不總是隨之變動。區隔與靠近空格強調左右兩邊的分割。 看完《代碼整潔之道》之后我受益匪淺,但等到自己實踐時卻很難按照書中給的建議編寫出整潔的代碼。一方面是規則太多,記不住,另一方面書上引用了大量示例代碼對這些規則進行佐證,在我記...
摘要:不要使用類函數終于,你不用再看到建議不要使用函數的提示了。因為從核心上完全移除了它們,這意味著請你移步至更好的類函數,或者更靈活的層。將從數據庫獲取一個元數據,如果您正在循環訪問特定文章的元數據,則可以在循環中使用它。 showImg(https://segmentfault.com/img/bV75FM?w=1024&h=534); 1. 不要使用 mysql_ 類函數 終于,你不用...
摘要:異步問題回調地獄首先,我們來看下異步編程中最常見的一種問題,便是回調地獄。同時使用也是異步編程最基礎和核心的一種解決思路?;?,目前也被廣泛運用,其是異步編程的一種解決方案,比傳統的回調函數解決方案更合理和強大。 關于 微信公眾號:前端呼啦圈(Love-FED) 我的博客:勞卜的博客 知乎專欄:前端呼啦圈 前言 在實際編碼中,我們經常會遇到Javascript代碼異步執行的場景...
閱讀 2230·2021-11-22 13:52
閱讀 3870·2021-11-10 11:36
閱讀 1415·2021-09-24 09:47
閱讀 1094·2019-08-29 13:54
閱讀 3368·2019-08-29 13:46
閱讀 1948·2019-08-29 12:16
閱讀 2116·2019-08-26 13:26
閱讀 3475·2019-08-23 17:10