⚠️本文爲掘金社區首發簽約文章,未獲受權禁止轉載java
6月中旬,可愛的產品大大給我提了一個臨時需求,須要我對商品建立/更新業務中由開放平臺對接而來的請求作一個Check,若是符合要求,則再作一段稍微複雜的邏輯處理
。編程
這種Easy程度的需求怎麼攔得住我,不到半天我就Coding,Push一鼓作氣,正當我準備點一杯喜茶開始摸魚的時候,我卻收到了一封郵件。設計模式
郵件裏有一堆的漢字和英文,但有幾個字赫然在目:緩存
您的代碼已被駁回。
markdown
當我經歷了茫然、震驚、不敢相信、最後無奈接受的情緒轉變後,問了評審的同事,爲何要駁回個人代碼,他說:「歷史代碼通常業務都很完整(跟屎山同樣了...),那若是有新的需求不得不依賴它的話,怎麼架構
作纔是最佳方案,讓代碼有更好的拓展性,你有想過嗎?」。app
我確定是沒有想的,因而乎,我懷着些許愧疚的心情,找到了架構師,但願他能爲我指點迷津。異步
亮架構:Kerwin,這段代碼是否是偷懶了?ide
try {
// 忽略歷史業務代碼,如下爲新增內容
} catch (Exception) {
// TODO
} finally {
SkuMainBean retVal = skuMainBridgeService.updateSkuBridgeMainBean(skuMainBean);
if(retVal != null){
// 商品建立/修改異步處理邏輯
SimpleThreadPool.executeRunnable(SimpleThreadPool.ThreadPoolName.openSkuHandle, () -> {
skuOperateBusinessService.checkOpenSkuReview(retVal);
});
}
}
複製代碼
我(雖然我以爲不妥,但仍是強裝鎮定):沒偷懶啊,你看這塊業務代碼既沒有影響原功能,又用線程池的方式異步處理,不會影響總體接口效率,並且還把複雜邏輯都封裝到了Business
層裏,這還叫偷懶嗎?函數
亮架構:你以爲這個商品建立/修改流程重要嗎?是否是我們的最核心的流程?下次產品再提新的需求,繼續 if 而後疊羅漢嗎
?我咋記得你說過你最討厭在代碼裏看到 if
呢?
我(小聲):我討厭看到別人的 if,可是本身的仍是能夠接受的...
亮架構(氣笑):不跟你耍貧嘴了,一塊兒想一想怎麼改吧。
PS:【找一個看起來合適的位置塞進去】這種方式是咱們使用最頻繁,影響面相對較小,開發效率最高的方式了,但它帶來的問題就是後期很差維護,並且隨着需求變多,它就會變得和疊羅漢同樣,原本一個很簡單的方法函數,會變成百上千行的 「屎山」,所以須要酌情使用。
我(開始思考):若是需求是不知足某種狀況便可終止執行,那這種狀況可太簡單了,就不絮叨了。
亮架構:其實仍是有一點可說的,好比你須要在不知足時返回標識符結果加細節緣由,你怎麼處理?
我:直接定義一個字符串而後返回,後續判斷字符串是否爲NULL便可。
亮架構:若是就是失敗了,且緣由也爲NULL或空字符串呢?其實咱們利用泛型
有更優雅的解決方案,好比這樣定義一個元組:
public class ValueMsgReturn<A, B> {
/** 結果 **/
private final A value;
/** 緣由 **/
private final B msg;
public ValueMsgReturn(A value, B msg) {
this.value = value;
this.msg = msg;
}
// 省略Get方法
}
複製代碼
這樣作的好處是,通用,簡單,沒必要定義重複的對象,你本身在代碼中試試就能明白它有多香,總體代碼就以下所示:
// 省略干擾代碼
ValueMsgReturn<Boolean, String> check = check();
if (check.getValue()) {
return check.getValue();
}
複製代碼
PS:此種狀況較爲簡單,但仍然有技巧優化代碼,詳情請見歷史文章:
我(繼續思考):你剛那種狀況太簡單了,迴歸正題,我們這個需求可使用觀察者模式解耦
啊!
亮架構(猶豫道):不是不能夠,但你想一下咱們須要改動哪些代碼吧。
我:觀察者的核心即通知方
+ 處理方
,若是咱們使用JDK自帶的觀察者模式的話,改動以下:
Observable
類Observer
接口亮架構:若是一段邏輯在設計之初就採用觀察者模式的話,那還不錯,但歷史代碼則不適合,由於它一個類裏面包含大量的其餘方法
,若是將來需求中有第二種須要通知的狀況,代碼就會更難維護,畢竟JDK觀察者模式是須要繼承Observable
類的,固然了,做爲一個備選方案也不是不行。
PS:以上描述的JDK觀察者模式對應的是JDK1.8版本,關於觀察者模式的詳情,請見歷史文章
我(忽然想起來):亮架構,你說用AOP
來處理合適嗎?
亮架構:通常狀況下咱們用AOP來作什麼動做呢?
我:個人話,通常會用做權限處理、日誌打印、緩存同步、特殊場景計數等等。
亮架構:是的,你想象一下若是咱們把這些業務邏輯都堆在切面裏會是什麼樣子?一個切點還好,兩個呢,十個呢?你們拿到新項目的時候都會參考前人的代碼風格,若是你開了一個壞的頭,其餘人就會跟着作一樣的事,很快代碼就會變成如同蜘蛛網通常,因此這種方式必定是要杜絕的。
我(忽然想起來):對了,我們的商品新建/修改都會有MQ的,我只用監聽MQ而後作業務處理
就行了。
亮架構:這個確定是可行的,就是有點殺雞焉用宰牛刀的感受,畢竟咱們須要處理的狀況只是MQ中的一小部分,並且萬一歷史代碼沒有發送MQ怎麼辦呢?
亮架構:你有了解過Spring Event
嗎?
我:之前研究過,確實用在這裏還蠻合適的。
PS:Spring Event是Spring體系中的事件通知機制,其原理能夠理解爲Spring實現的觀察者模式。
注:上文中的簡單觀察者模式指的是JDK(1.8)實現的觀察者模式。
// 如下爲Demo代碼
@RestController
public class EventRequest implements ApplicationContextAware {
private ApplicationContext appContext;
@RequestMapping("/testEvent")
public String testEventSave(String name) {
appContext.publishEvent(new User(this, name));
return "ok";
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
}
}
複製代碼
// 監聽者
@Component
public class WebEventListener {
/** * 僅監聽字段值爲 foo 時,類爲 User.class 時 */
@EventListener(classes = User.class, condition = "#event.name == 'foo'")
public void listen(User event){
// TODO
}
/** * 監聽 User.class 狀況 */
@EventListener(classes = User.class)
public void listen1(User event){
// TODO
}
}
複製代碼
亮架構:是的,這個Demo就很能反映它的優點之處了
我:是的,並且它還能夠支持異步事件處理!
亮架構(停頓了一下):你以爲支持異步是它獨特的優點嗎?哈哈哈,即便是同步監聽到事件,你只要用線程池異步處理就行了。可以自然異步化,只是錦上添花的東西,不要弄混了哦。固然了,每種技術和特性都有其獨特的使用場景,在使用的時候須要注意它的特殊狀況,好比:
我(開心):若是我用Spring Event的話,我只須要稍微改動一下就行了,代碼的拓展性,可維護性一會兒就上來了,不過剛我們聊了那麼多方式方法,怎麼感受全是觀察者模式
啊?
亮架構:是的,不管是JDK的仍是Spring,亦或是AOP、MQ,這些通通都是觀察者模式的思想,畢竟觀察者模式的特色就是解耦
。
我:難道不能用別的設計模式思想嗎?
亮架構:固然能夠,就是改動可能略大一點,畢竟這個類都快幾千行了,仍是儘可能少加東西了。
我:好比呢,能夠用什麼其餘的方式?
亮架構:額...你既然想聽的話,能夠這樣,回顧一下你最初的代碼:
finally {
SkuMainBean retVal = skuMainBridgeService.updateSkuBridgeMainBean(skuMainBean);
if(retVal != null){
// 商品建立/修改異步處理邏輯
SimpleThreadPool.executeRunnable(SimpleThreadPool.ThreadPoolName.openSkuHandle, () -> {
skuOperateBusinessService.checkOpenSkuReview(retVal);
});
}
}
複製代碼
在這個業務方法裏處理的確定是skuMainBean
對象,由於整個方法都是在操做它,那咱們徹底能夠抽象出一個個策略類,而後利用工廠來處理,好比改爲這樣:
// 修改後代碼
finally {
skuMainBeanFactory.checkAndHandle(skuMainBean);
}
// 工廠方法
public void checkAndHandle (SkuMainBean skuMainBean) {
for (策略集合: 策略) {
if (check(skuMainBean)) {
// TODO
}
}
}
複製代碼
亮架構:你看這樣是否是也具備很好的拓展性?
我(興奮):是的,我忽然感受這種方式和SpringEvent有殊途同歸之妙!
亮架構(笑了笑):孺子可教也,這種策略+工廠的方式是基於接口編程,經過check方法判斷是否須要處理,而SpringEvent說白了是經過事件的傳播,即方法直接調用來判斷是否須要處理,本質都是同樣的,那你知道將來的新需求你該怎麼寫了嗎?
我(興奮):我知道了,要寫可拓展性的代碼,像我今天改的這種代碼就不行,太垃圾了!
亮架構(搖了搖頭,起身走了):Kerwin,你錯了,你今天改的歷史代碼在當時能夠說是最佳實踐了,只是由於你遇到了以前的設計者未考慮到的問題而已。咱們講設計模式、講七大原則,講不要過分設計,就是爲了你如今出現的狀況,咱們在編碼過程當中可能會遇到千奇百怪的代碼,咱們能夠抱怨,能夠吐槽,但記住,不要爲了某些需求就把原本漂亮的代碼變成屎山。因此你須要去學習編程的思想,學習設計的思想。
我(大聲):那,架構師!若是有一段代碼已經爛到不能再爛了呢!
「那就把它重構了!而後把做者的名字記下來,狠狠的吐槽他!🤪」
回顧全文作一個總結,若是你的需求是容許前置校驗返回的,那麼絕不猶豫的CheckAndReturn便可!可是,若是你的需求和我同樣,那麼推薦如下幾種方案:
那麼請容許我推薦一下以前的文章:設計模式總篇:從爲何須要原則到實際落地
若是你以爲這篇內容對你有幫助的話: