摘要:源碼剖析之設(shè)計(jì)模式鑒賞策略模式小結(jié)在這篇文章中筆者和大家分享幾個(gè)減少的小由于這些都會(huì)有一定的限制因此還向大家介紹了幾個(gè)能夠避免寫出糟糕的的設(shè)計(jì)模式并使用觀察者模式簡(jiǎn)單的改進(jìn)了仲裁者模式的例子
本文首發(fā)于數(shù)據(jù)浮云:https://mp.weixin.qq.com/s?__...
在寫代碼的日常中,if...else語句是極為常見的.正因其常見性,很多同學(xué)在寫代碼的時(shí)候并不會(huì)去思考其在目前代碼中的用法是否妥當(dāng).而隨著項(xiàng)目的日漸發(fā)展,糟糕的if...else語句將會(huì)充斥在各處,讓項(xiàng)目的可維護(hù)性急劇下降.故在這篇文章中,筆者想和大家談?wù)勅绾伪苊鈱懗鲈愀?b>if...else語句.
由于脫密等原因.文章中的示例代碼將會(huì)用一些開源軟件的代碼或者抽象過的生產(chǎn)代碼作為示范.問題代碼
當(dāng)我們看到一組if...else時(shí),一般是不會(huì)有什么閱讀負(fù)擔(dān)的.但當(dāng)我們看到這樣的代碼時(shí):
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(); }
看完這兩段代碼,相信大家和我的心情是一樣的:
閱讀它們的負(fù)擔(dān)實(shí)在是太大了——我們要記住好幾個(gè)邏輯判斷分支,才能知道到底什么情況下才能得到那個(gè)結(jié)果.更別說維護(hù)的成本有多高了,每次維護(hù)時(shí)都要讀一遍,然后再基于此來改.長(zhǎng)此以往,我們的代碼就變成"箭頭式代碼"了.
//............... //............... //............... //............... //............... //............... //............... //............... //............... //...............目標(biāo)和關(guān)鍵指標(biāo)
前面說過,我們的目標(biāo)是減少糟糕的if...else代碼.那么什么是糟糕的if...else代碼呢?我們可以簡(jiǎn)單的總結(jié)一下:
兩重以上的嵌套
一個(gè)邏輯分支的判斷條件有多個(gè),如:A && B || C這種.其實(shí)這也可以看作變種的嵌套
這樣就可以看出來,我們的關(guān)鍵指標(biāo)就是減少嵌套.
常見Tips 1. 三元表達(dá)式三元表達(dá)式在代碼中也是較為常見的,它可以簡(jiǎn)化一些if...else,如:
public Object getFromOpaque(String key) { return opaque == null ? null : opaque.get(key); }
為什么說是一些呢?因此三元表達(dá)式必須要有一個(gè)返回值.
這種情況下就沒法使用三元表達(dá)式
public void putToOpaque(String key, Object value) { if (opaque == null) { opaque = new LinkedHashMap(); } opaque.put(key, value); }2. switch case
在Java中,switch可以關(guān)注一個(gè)變量( byte short int 或者 char,從Java7開始支持String),然后在每個(gè)case中比對(duì)是否匹配,是的話則進(jìn)入這個(gè)分支.
在通常情況下,switch case的可讀性比起if...else會(huì)好一點(diǎn).因?yàn)閕f中可以放復(fù)雜的表達(dá)式,而switch則不行.話雖如此,嵌套起來還是會(huì)很惡心.
因此,如果僅僅是對(duì) byte,short,int和char以String簡(jiǎn)單的值判斷,可以考慮優(yōu)先使用switch.
3. 及時(shí)回頭/* 查找年齡大于18歲且為男性的學(xué)生列表 */ 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("獲取學(xué)生列表失敗"); } }else { throw new MyException("獲取老師信息失敗"); } } else { throw new MyException("獲取學(xué)生信息失敗"); } return result; }
針對(duì)這種情況,我們應(yīng)該及時(shí)拋出異常(或者說return),保證正常流程在外層,如:
/* 查找年齡大于18歲且為男性的學(xué)生列表 */ public ArrayList使用設(shè)計(jì)模式getStudents(int uid){ ArrayList result = new ArrayList (); Student stu = getStudentByUid(uid); if (stu == null) { throw new MyException("獲取學(xué)生信息失敗"); } Teacher teacher = stu.getTeacher(); if(teacher == null){ throw new MyException("獲取老師信息失敗"); } ArrayList students = teacher.getStudents(); if(students == null){ throw new MyException("獲取學(xué)生列表失敗"); } for(Student student : students){ if(student.getAge() > 18 && student.getGender() == MALE){ result.add(student); } } return result; }
除了上面的幾個(gè)tips,我們還可以通過設(shè)計(jì)模式來避免寫出糟糕的if...else語句.在這一節(jié),我們將會(huì)提到下面幾個(gè)設(shè)計(jì)模式:
State模式
Mediator模式
Observer模式
Strategy模式
1. State模式在代碼中,我們經(jīng)常會(huì)判斷一些業(yè)務(wù)對(duì)象的狀態(tài)來決定在當(dāng)前的調(diào)用下它該怎么做.我們舉個(gè)例子,現(xiàn)在我們有一個(gè)銀行的接口:
public interface Bank { /** * 銀行上鎖 * */ void lock(); /** * 銀行解鎖 * */ void unlock(); /** * 報(bào)警 * */ void doAlarm(); }
讓我們來看一下它的實(shí)現(xiàn)類
public class BankImpl implements Bank { @Override public void lock() { //保存這條記錄 } @Override public void unlock() { if ((BankState.Day == getCurrentState())) { //白天解鎖正常 //僅僅保存這條記錄 } else if (BankState.Night == getCurrentState()) { //晚上解鎖,可能有問題 //保存這條記錄,并報(bào)警 doAlarm(); } } @Override public void doAlarm() { if ((BankState.Day == getCurrentState())) { //白天報(bào)警,聯(lián)系當(dāng)?shù)鼐?,并保留這條記錄 } else if (BankState.Night == getCurrentState()) { //晚上報(bào)警,可能有事故,不僅聯(lián)系當(dāng)?shù)鼐?,還需要協(xié)調(diào)附近的安保人員,并保留這條記錄 } } private BankState getCurrentState() { return BankState.Day; } }
顯然,我們涉及到了一個(gè)狀態(tài):
public enum BankState { Day, Night }
在不同的狀態(tài)下,同一件事銀行可能會(huì)作出不同的反應(yīng).這樣顯然很挫,因?yàn)樵谡鎸?shí)業(yè)務(wù)場(chǎng)景下,業(yè)務(wù)的狀態(tài)可能不僅僅只有兩種.每多一種,就要多寫一個(gè)if...else.所以,如果按照狀態(tài)模式,可以這樣來重構(gòu):
public class BankDayImpl implements Bank { @Override public void lock() { //保存這條記錄 } @Override public void unlock() { //白天解鎖正常 //僅僅保存這條記錄 } @Override public void doAlarm() { //白天報(bào)警,聯(lián)系當(dāng)?shù)鼐?,并保留這條記錄 } }
public class BankNightImpl implements Bank { @Override public void lock() { //保存這條記錄 } @Override public void unlock() { //晚上解鎖,可能有問題 //保存這條記錄,并報(bào)警 doAlarm(); } @Override public void doAlarm() { //晚上報(bào)警,可能有事故,不僅聯(lián)系當(dāng)?shù)鼐?,還需要協(xié)調(diào)附近的安保人員,并保留這條記錄 } }2. Mediator模式
在本文的第一段的代碼中,其實(shí)是ZStack 2.0.5版本中某處的代碼,它用來防止用戶使用Cli時(shí)傳入不當(dāng)?shù)膮?shù),導(dǎo)致后面的邏輯運(yùn)行不正常.為了方便理解,我們可以對(duì)其規(guī)則做一個(gè)簡(jiǎn)化,并畫成圖的樣子來供大家理解.
假設(shè)這是一個(gè)提交定時(shí)重啟VM計(jì)劃任務(wù)的“上古級(jí)”界面(因?yàn)楹玫慕换ピO(shè)計(jì)師一定不會(huì)把界面設(shè)計(jì)成這樣吧...).規(guī)則大概如下:
2.1 Simple類型的SchedulerSimple類型的Scheduler,可以根據(jù)Interval,RepeatCount,StartTime來定制一個(gè)任務(wù).
2.1.1 當(dāng)選擇Simple類型的任務(wù)時(shí),Interval,StartTime這兩個(gè)參數(shù)必填 2.1.2 當(dāng)填好Interval,和StartTime,這個(gè)時(shí)候已經(jīng)可以提交定時(shí)任務(wù)了 2.1.3 RepeatCount是個(gè)可選參數(shù) 2.2 Cron類型的SchedulerCron類型的Scheduler,可以根據(jù)cron表達(dá)式來提交任務(wù).
在這里請(qǐng)大家思考一個(gè)問題,如果要寫這樣的一個(gè)界面,該怎么寫?——在一個(gè)windows類里,先判斷上面的可選欄是哪種類型,然后根據(jù)文本框里的值是否被填好決定提交按鈕屬否亮起...這算是基本邏輯.上面還沒有提到邊界值的校驗(yàn)——這些邊界值的校驗(yàn)往往會(huì)散落在各個(gè)組件的實(shí)例里,并通過互相通信的方式來判斷自己應(yīng)該做出什么樣的變化,相信大家已經(jīng)意識(shí)到了直接無腦堆if...else代碼的恐怖之處了吧.
2.3 使用仲裁者改善它接下來,我們將會(huì)貼上來一些偽代碼,方便讀者更好的理解這個(gè)設(shè)計(jì)模式
/** * 仲裁者的成員接口 * */ public interface Colleague { /** * 設(shè)置成員的仲裁者 * */ void setMediator(Mediator mediator); /** * 設(shè)置成員是否被啟用 * */ void setColleagueEnabled(boolean enabled); }
/** * 仲裁者接口 * */ public interface Mediator { /** * 當(dāng)一個(gè)組員發(fā)生狀態(tài)變化時(shí),調(diào)用此方法 * */ void colllectValueChanged(String value); }
/** * 含有textField的組件應(yīng)當(dāng)實(shí)現(xiàn)接口 */ public interface TextField { String getText(); }
/** * 當(dāng)一個(gè)組件的值發(fā)生變化時(shí),ValueListener會(huì)收到相應(yīng)通知 * */ public interface ValueListener { /** * 當(dāng)組員的值變化時(shí),這個(gè)接口會(huì)被調(diào)用 * */ void valueChanged(String str); }
定義了幾個(gè)接口之后,我們開始編寫具體的類:
用于表示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) { //當(dāng)true時(shí)去掉下劃線,并允許被按下 } @Override public void valueChanged(String str) { mediator.colllectValueChanged(str); } }
以及幾個(gè)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) { //當(dāng)true時(shí)去掉下劃線,并允許值輸入 } @Override public void valueChanged(String str) { mediator.colllectValueChanged(str); } @Override public String getText() { return text; } }
SchedulerValidator的具體實(shí)現(xiàn)SchedulerValidatorImpl就不貼上來了,里面僅僅是一些校驗(yàn)邏輯.
接著是我們的主類,也就是知道全局狀態(tài)的窗口類
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(); } /** * 當(dāng)一個(gè)組員發(fā)生狀態(tài)變化時(shí),調(diào)用此方法 * 組件初始化時(shí)都為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(); } }
在這個(gè)設(shè)計(jì)模式中,所有實(shí)例狀態(tài)的判斷全部都交給了仲裁者這個(gè)實(shí)例來判斷,而不是互相去通信.在目前的場(chǎng)景來看,其實(shí)涉及的實(shí)例還不是特別多,但在一個(gè)復(fù)雜的系統(tǒng)中,涉及的實(shí)例將會(huì)變得非常多.假設(shè)現(xiàn)在有A,B兩個(gè)實(shí)例,那么會(huì)有兩條通信線路:
而有A,B,C時(shí),則有6條線路
當(dāng)有4個(gè)實(shí)例時(shí),將會(huì)有12個(gè)通信線路
當(dāng)有5個(gè)實(shí)例時(shí),會(huì)有20個(gè)通信線路
以此類推...
這個(gè)時(shí)候,仲裁者模式的優(yōu)點(diǎn)就發(fā)揮出來了——這些邏輯如果分散在各個(gè)角色中,代碼將會(huì)變得難以維護(hù).
3. Observer模式ZStack源碼剖析之設(shè)計(jì)模式鑒賞——三駕馬車
結(jié)合本文的主題,其實(shí)觀察者模式做的更多的是將if...else拆分到屬于其自己的模塊中.以ZStack的為例,當(dāng)主存儲(chǔ)重連時(shí),主存儲(chǔ)模塊可能要讓模塊A和模塊B去做一些事,如果不使用觀察者模式,那么代碼就會(huì)都耦合在主存儲(chǔ)模塊下,拆開if...else也就不太可能了.
改進(jìn)之前的仲裁者例子觀察者模式一般是通過事件驅(qū)動(dòng)的方式來通信的,因此Observer和Subject一般都是松耦合的——Subject發(fā)出通知時(shí)并不會(huì)指定消費(fèi)者.而在之前仲裁者模式的例子中,仲裁者和成員之間緊耦合的(即他們必須互相感知),因此可以考慮通過觀察者模式來改進(jìn)它.
4. Strategy模式通常在編程時(shí),算法(策略)會(huì)被寫在具體方法中,這樣會(huì)導(dǎo)致具體方法中充斥著條件判斷語句。但是Strategy卻特意將算法與其他部分剝離開來,僅僅定義了接口,然后再以委托的方式來使用算法。然而這種做法正是讓程序更加的松耦合(因?yàn)槭褂梦锌梢苑奖愕恼w替換算法),使得整個(gè)項(xiàng)目更加茁壯。
ZStack源碼剖析之設(shè)計(jì)模式鑒賞——策略模式小結(jié)
在這篇文章中,筆者和大家分享幾個(gè)減少if...else的小tips,由于這些tips都會(huì)有一定的限制,因此還向大家介紹了幾個(gè)能夠避免寫出糟糕的if...else的設(shè)計(jì)模式,并使用觀察者模式簡(jiǎn)單的改進(jìn)了仲裁者模式的例子.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/73065.html
摘要:系列文章工匠善用變量改善代碼質(zhì)量序言編寫條件分支代碼是編碼過程中不可或缺的一部分。而進(jìn)行條件分支判斷時(shí)用到的也是這個(gè)值重點(diǎn)來了,雖然所有用戶類實(shí)例的布爾值都是真。 歡迎大家前往騰訊云+社區(qū),獲取更多騰訊海量技術(shù)實(shí)踐干貨哦~ 本文由鵝廠優(yōu)文發(fā)表于云+社區(qū)專欄 作者:朱雷 | 騰訊IEG高級(jí)工程師 『Python 工匠』是什么? 我一直覺得編程某種意義是一門『手藝』,因?yàn)閮?yōu)雅而高效的代碼...
摘要:天生缺乏邏輯性的問題導(dǎo)致了預(yù)處理器的出現(xiàn)。這會(huì)導(dǎo)致圈復(fù)雜度問題。圈復(fù)雜度對(duì)于來說可能是一種比較高階的原則,但如果我們通過它來考量那些蘊(yùn)含在我們寫的選擇器中的邏輯性,那我們也許就能寫出更加優(yōu)秀的代碼。 本文在征得原作者 @csswizardry 同意的情況下,翻譯自他博客中的文章:Cyclomatic Complexity: Logic in CSS。最初發(fā)布于我的個(gè)人博客:咀嚼之...
摘要:看完代碼整潔之道之后我受益匪淺,但等到自己實(shí)踐時(shí)卻很難按照書中給的建議編寫出整潔的代碼。意味著新人除了了解代碼邏輯之外,還需要學(xué)習(xí)這種編碼語言。代碼在演化,注釋卻不總是隨之變動(dòng)。區(qū)隔與靠近空格強(qiáng)調(diào)左右兩邊的分割。 看完《代碼整潔之道》之后我受益匪淺,但等到自己實(shí)踐時(shí)卻很難按照書中給的建議編寫出整潔的代碼。一方面是規(guī)則太多,記不住,另一方面書上引用了大量示例代碼對(duì)這些規(guī)則進(jìn)行佐證,在我記...
摘要:不要使用類函數(shù)終于,你不用再看到建議不要使用函數(shù)的提示了。因?yàn)閺暮诵纳贤耆瞥怂鼈?,這意味著請(qǐng)你移步至更好的類函數(shù),或者更靈活的層。將從數(shù)據(jù)庫獲取一個(gè)元數(shù)據(jù),如果您正在循環(huán)訪問特定文章的元數(shù)據(jù),則可以在循環(huán)中使用它。 showImg(https://segmentfault.com/img/bV75FM?w=1024&h=534); 1. 不要使用 mysql_ 類函數(shù) 終于,你不用...
摘要:異步問題回調(diào)地獄首先,我們來看下異步編程中最常見的一種問題,便是回調(diào)地獄。同時(shí)使用也是異步編程最基礎(chǔ)和核心的一種解決思路?;冢壳耙脖粡V泛運(yùn)用,其是異步編程的一種解決方案,比傳統(tǒng)的回調(diào)函數(shù)解決方案更合理和強(qiáng)大。 關(guān)于 微信公眾號(hào):前端呼啦圈(Love-FED) 我的博客:勞卜的博客 知乎專欄:前端呼啦圈 前言 在實(shí)際編碼中,我們經(jīng)常會(huì)遇到Javascript代碼異步執(zhí)行的場(chǎng)景...
閱讀 2225·2021-11-22 13:52
閱讀 3866·2021-11-10 11:36
閱讀 1408·2021-09-24 09:47
閱讀 1092·2019-08-29 13:54
閱讀 3366·2019-08-29 13:46
閱讀 1946·2019-08-29 12:16
閱讀 2113·2019-08-26 13:26
閱讀 3475·2019-08-23 17:10