自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我寫的代碼,又被CTO罵了......

開發(fā) 前端 開發(fā)工具
大多數(shù)時候我都是寫一些業(yè)務(wù)代碼,可能一堆 CRUD 就能解決問題,但是這樣的工作對技術(shù)人的提升并不多,如何讓自己從業(yè)務(wù)中解脫出來找到寫代碼的樂趣呢,我做過一些嘗試,使用設(shè)計模式改善自己的業(yè)務(wù)代碼就是其中的一種。

大多數(shù)時候我都是寫一些業(yè)務(wù)代碼,可能一堆 CRUD 就能解決問題,但是這樣的工作對技術(shù)人的提升并不多,如何讓自己從業(yè)務(wù)中解脫出來找到寫代碼的樂趣呢,我做過一些嘗試,使用設(shè)計模式改善自己的業(yè)務(wù)代碼就是其中的一種。

 

[[345303]] 

圖片來自 Pexels

“你這代碼寫的像坨屎”,今天我的代碼又被當(dāng)作典型被 CTO 罵了......于是他給我的建議如下:

責(zé)任鏈設(shè)計模式

模式定義

請求在一個鏈條上處理,鏈條上的受理者處理完畢之后決定是繼續(xù)往后傳遞還是中斷當(dāng)前處理流程。

適用場景

適用于多節(jié)點的流程處理,每個節(jié)點完成各自負(fù)責(zé)的部分,節(jié)點之間不知道彼此的存在,比如 OA 的審批流,Java Web 開發(fā)中的 Filter 機(jī)制。

舉一個生活中的例子,筆者之前租房的時候遇到了所謂的黑中介,租的時候感覺自己是上帝,但是壞了東西找他修的時候就像個孫子一樣。

中介讓我找門店客服,門店客服又讓我找房東,房東又讓我找她家老公,最終好說歹說才把這事了了(租房一定要找正規(guī)中介)。

實踐經(jīng)驗

筆者目前所做的業(yè)務(wù)是校園團(tuán)餐的聚合支付,業(yè)務(wù)流程很簡單:

  • 學(xué)生打開手機(jī)付款碼支付。
  • 食堂大媽使用機(jī)具掃付款碼收款。

大學(xué)食堂有個背景是這樣的,食堂有補(bǔ)貼,菜品比較便宜,所以學(xué)校是不愿意讓社會人士去學(xué)校食堂消費的,鑒于此,我們在支付之前加了一套是否允許支付的檢驗邏輯。

大體如下:

  • 某檔口只允許某類用戶用戶消費,比如教師檔口只允許教師消費,學(xué)生檔口不允許校外用戶消費。
  • 某個檔口一天只允許某類用戶消費幾次,比如教師食堂一天只允許學(xué)生消費一次。
  • 是否允許非清真學(xué)生消費,比如某些清真餐廳,是不允許非清真學(xué)生消費的。

針對這幾類情況我建立了三類過濾器,分別是:

  • SpecificCardUserConsumeLimitFilter:按用戶類型判斷是否允許消費。
  • DayConsumeTimesConsumeLimitFilter:按日消費次數(shù)判斷是否允許消費。
  • MuslimConsumeLimitFilter:非清真用戶是否允許消費。

判斷邏輯是先通過 SpecificCardUserConsumeLimitFilter 判斷當(dāng)前用戶是否可以在此檔口消費。

如果允許繼續(xù)由 DayConsumeTimesConsumeLimitFilter 判斷當(dāng)天消費次數(shù)是否已用完;如果未用完繼續(xù)由 MuslimConsumeLimitFilter 判斷當(dāng)前用戶是否滿足清真餐廳的就餐條件,前面三條判斷,只要有一個不滿足就提前返回。

部分代碼如下:

  1. public boolean canConsume(String uid,String shopId,String supplierId){ 
  2.     //獲取用戶信息,用戶信息包含類型(student:學(xué)生,teacher:老師,unknown:未知用戶)、名族(han:漢族,mg:蒙古族) 
  3.     UserInfo userInfo = getUserInfo(uid); 
  4.     //獲取消費限制信息,限制信息包含是否允許非清真消費、每種類型的用戶是否允許消費以及允許消費的次數(shù) 
  5.    ConsumeConfigInfo consumeConfigInfo = getConsumeConfigInfo(shopId,supplierId)  
  6.  
  7.  
  8.     // 構(gòu)造消費限制過濾器鏈條 
  9.     ConsumeLimitFilterChain filterChain = new ConsumeLimitFilterChain(); 
  10.     filterChain.addFilter(new SpecificCardUserConsumeLimitFilter()); 
  11.     filterChain.addFilter(new DayConsumeTimesConsumeLimitFilter()); 
  12.     filterChain.addFilter(new MuslimConsumeLimitFilter()); 
  13.     boolean checkResult = filterChain.doFilter(filterChain, schoolMemberInfo, consumeConfigInfo); 
  14.  
  15.     //filterChain.doFilter方法 
  16.    public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo, 
  17.              ConsumeConfigInfo consumeConfigInfo ){ 
  18.         //迭代調(diào)用過濾器 
  19.         if(index<filters.size()){ 
  20.             return filters.get(index++).doFilter(filterChain, userInfo, consumeConfigInfo); 
  21.         } 
  22.     } 
  23.  
  24.     //SpecificCardUserConsumeLimitFilter.doFilter方法 
  25.      public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo, 
  26.              ConsumeConfigInfo consumeConfigInfo ){ 
  27.                 //獲取某一類型的消費限制,比如student允許消費,unknown不允許消費 
  28.         CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo); 
  29.  
  30.         // 判斷當(dāng)前卡用戶是否允許消費 
  31.         if (consumeCardConfig != null) { 
  32.             if ((!CAN_PAY.equals(cardConsumeConfig .getEnabledPay()))) { 
  33.                 return false
  34.             } 
  35.         } 
  36.  
  37.                 //其余情況,繼續(xù)往后傳遞 
  38.             return filterChain.doFilter(filterChain, memberInfo, consumeConfig); 
  39.         } 
  40.  
  41.     //DayConsumeTimesConsumeLimitFilter.doFilter方法 
  42.      public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo, 
  43.              ConsumeConfigInfo consumeConfigInfo ){ 
  44.                 //獲取某一類型的消費限制,比如student可以消費2次 
  45.         CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo); 
  46.  
  47.                 //獲取當(dāng)前用戶今天的消費次數(shù) 
  48.                 int consumeCnt = getConsumeCnt(userInfo)         
  49.         if(consumeCnt >= cardConsumeConfig.getDayConsumeTimesLimit()){ 
  50.                     return false
  51.                 } 
  52.  
  53.                 //其余情況,繼續(xù)往后傳遞 
  54.                 return filterChain.doFilter(filterChain, memberInfo, consumeConfig); 
  55.         } 

總結(jié):將每種限制條件的判斷邏輯封裝到了具體的 Filter 中,如果某種限制條件的邏輯有修改不會影響其他條件,如果需要新加限制條件只需要重新構(gòu)造一個 Filter 織入到 FilterChain 上即可。

策略設(shè)計模式

模式定義

定義一系列的算法,把每一個算法封裝起來,并且使它們可相互替換。

適用場景

主要是為了消除大量的 if else 代碼,將每種判斷背后的算法邏輯提取到具體的策略對象中,當(dāng)算法邏輯修改時對使用者無感知,只需要修改策略對象內(nèi)部邏輯即可。

這類策略對象一般都實現(xiàn)了某個共同的接口,可以達(dá)到互換的目的。

實踐經(jīng)驗

筆者之前有個需求是用戶掃碼支付以后向檔口的收銀設(shè)備推送一條支付消息,收銀設(shè)備收到消息以后會進(jìn)行語音播報,邏輯很簡單,就是調(diào)用推送平臺推送一條消息給設(shè)備即可。

但是由于歷史原因,某些設(shè)備對接的推送平臺是不一樣的,A 類設(shè)備優(yōu)先使用信鴿推送,如果失敗了需要降級到長輪詢機(jī)制,B 類設(shè)備直接使用自研的推送平臺即可。

還有個現(xiàn)狀是 A 類和 B 類的消息格式是不一樣的(不同的團(tuán)隊開發(fā),后期被整合到一起)。

鑒于此,我抽象出 PushStrategy 接口,其具體的實現(xiàn)有 IotPushStrategy 和 XingePushStrategy,分別對應(yīng)自研推送平臺的推送策略和信鴿平臺的推送策略,使用者時針對不同的設(shè)備類型使用不同的推送策略即可。

部分代碼如下:

  1. /** 
  2.  * 推送策略 
  3.  * / 
  4. public interface PushStrategy { 
  5.     /** 
  6.          @param deviceVO設(shè)備對象,包扣設(shè)備sn,信鴿pushid 
  7.          @param content,推送內(nèi)容,一般為json 
  8.         */ 
  9.     public CallResult push(AppDeviceVO deviceVO, Object content); 
  10.  
  11. IotPushStrategy implements PushStrategy{ 
  12.         /** 
  13.          @param deviceVO設(shè)備對象,包扣設(shè)備sn,信鴿pushid 
  14.          @param content,推送內(nèi)容,一般為json 
  15.         */ 
  16.     public CallResult push(AppDeviceVO deviceVO, Object content){ 
  17.             //創(chuàng)建自研推送平臺需要的推送報文 
  18.             Message message = createPushMsg(deviceVO,content); 
  19.  
  20.             //調(diào)用推送平臺推送接口 
  21.             IotMessageService.pushMsg(message); 
  22.         } 
  23.  
  24. XingePushStrategy implements PushStrategy{ 
  25.         /** 
  26.          @param deviceVO設(shè)備對象,包扣設(shè)備sn,信鴿pushid 
  27.          @param content,推送內(nèi)容,一般為json 
  28.         */ 
  29.     public CallResult push(AppDeviceVO deviceVO, Object content){ 
  30.             //創(chuàng)建信鴿平臺需要的推送報文 
  31.             JSONObject jsonObject = createPushMsg(content); 
  32.  
  33.             //調(diào)用推送平臺推送接口 
  34.             if(!XinggePush.pushMsg(message)){ 
  35.                 //降級到長輪詢 
  36.                 ... 
  37.             } 
  38.         } 
  39.  
  40. /** 
  41. 消息推送Service 
  42. */ 
  43. MessagePushService{ 
  44.     pushMsg(AppDeviceVO deviceVO, Object content){ 
  45.         if(A設(shè)備){ 
  46.             XingePushStrategy.push(deviceVO,content); 
  47.         } else if(B設(shè)備){ 
  48.             IotPushStrategy.push(deviceVO,content); 
  49.         } 
  50.     } 

總結(jié):將每種通道的推送邏輯封裝到了具體的策略中,某種策略的變更不會影響其他策略,由于實現(xiàn)了共同接口,所以策略可以互相替換,對使用者友好。

比如 Java ThreadPoolExecutor 中的任務(wù)拒絕策略,當(dāng)線程池已經(jīng)飽和的時候會執(zhí)行拒絕策略,具體的拒絕邏輯被封裝到了 RejectedExecutionHandler 的 rejectedExecution 中。

模板設(shè)計模式

模式定義

模板的價值就在于骨架的定義,骨架內(nèi)部將問題處理的流程已經(jīng)定義好,通用的處理邏輯一般由父類實現(xiàn),個性化的處理邏輯由子類實現(xiàn)。

比如炒土豆絲和炒麻婆豆腐,大體邏輯都是:

  • 切菜
  • 放油
  • 炒菜
  • 出鍋

1,2,4 都差不多,但是第 3 步是不一樣的,炒土豆絲得拿鏟子翻炒,但是炒麻婆豆腐得拿勺子輕推,否則豆腐會爛(疫情宅在家,學(xué)了不少菜)。

使用場景

不同場景的處理流程,部分邏輯是通用的,可以放到父類中作為通用實現(xiàn),部分邏輯是個性化的,需要子類去個性實現(xiàn)。

實踐經(jīng)驗

還是接著之前語音播報的例子來說,后期我們新加了兩個需求:

  • 消息推送需要增加 trace。
  • 有些通道推送失敗需要重試。

所以現(xiàn)在的流程變成了這樣:

  • trace 開始。
  • 通道開始推送。
  • 是否允許重試,如果允許執(zhí)行重試邏輯。
  • trace 結(jié)束。

其中 1 和 4 是通用的,2 和 3 是個性化的,鑒于此我在具體的推送策略之前增加了一層父類的策略,將通用邏輯放到了父類中。

修改后的代碼如下:

  1. abstract class AbstractPushStrategy implements PushStrategy{ 
  2.     @Override 
  3.     public CallResult push(AppDeviceVO deviceVO, Object content) { 
  4.         //1.構(gòu)造span 
  5.         Span span = buildSpan(); 
  6.         //2.具體通道推送邏輯由子類實現(xiàn) 
  7.         CallResult callResult = doPush(deviceVO, content); 
  8.  
  9.         //3.是否允許重試邏輯由子類實現(xiàn),如果允許執(zhí)行重試邏輯 
  10.         if(!callResult.isSuccess() && canRetry()){ 
  11.             doPush(deviceVO, content); 
  12.         } 
  13.  
  14.         //4.trace結(jié)束 
  15.         span.finish()  
  16.     } 
  17.  
  18.     //具體推送邏輯由子類實現(xiàn) 
  19.     protected abstract CallResult doPush(AppDeviceVO deviceDO, Object content) ; 
  20.  
  21.     //是否允許重試由子類實現(xiàn),有些通道之前沒有做消息排重,所有不能重試 
  22.     protected abstract boolean canRetry(CallResult callResult); 
  23.  
  24.  
  25. XingePushStrategy extends AbstractPushStrategy{ 
  26.     @Override 
  27.     protected CallResult doPush(AppDeviceVO deviceDO, Object content) { 
  28.         //執(zhí)行推送邏輯 
  29.     } 
  30.  
  31.     @Override 
  32.     protected boolean canRetry(CallResult callResult){ 
  33.         return false 
  34.     } 

總結(jié):通過模板定義了流程,將通用邏輯放在父類實現(xiàn),減少了重復(fù)代碼,個性化邏輯由子類自己實現(xiàn),子類間修改代碼互不干擾也不會破壞流程。

觀察者設(shè)計模式

模式定義

顧名思義,此模式需要有觀察者(Observer)和被觀察者(Observable)兩類角色。

當(dāng) Observable 狀態(tài)變化時會通知 Observer,Observer 一般會實現(xiàn)一類通用的接口。

比如 java.util.Observer,Observable 需要通知 Observer 時,逐個調(diào)用 Observer 的 update 方法即可,Observer 的處理成功與否不應(yīng)該影響 Observable 的流程。

使用場景

一個對象(Observable)狀態(tài)改變需要通知其他對象,Observer 的存在不影響 Observable 的處理結(jié)果,Observer 的增刪對 Observable 無感知。

比如 Kafka 的消息訂閱,Producer 發(fā)送一條消息到 Topic,至于是 1 個還是 10 個 Consumer 訂閱這個 Topic,Producer 是不需要關(guān)注的。

實踐經(jīng)驗

在責(zé)任鏈設(shè)計模式那塊我通過三個 Filter 解決了消費限制檢驗的問題,其中有一個 Filter 是用來檢驗消費次數(shù)的,我這里只是讀取用戶的消費次數(shù),那么消費次數(shù)的累加是怎么完成的呢?

其實累加這塊就用到了觀察者模式,具體來講是這樣,當(dāng)交易系統(tǒng)收到支付成功回調(diào)時會通過 Spring 的事件機(jī)制發(fā)布“支付成功事件”。

這樣負(fù)責(zé)累加消費次數(shù)和負(fù)責(zé)語音播報的訂閱者就會收到“支付成功事件”,進(jìn)而做各自的業(yè)務(wù)邏輯。

畫個簡單的圖描述一下:

代碼結(jié)構(gòu)大體如下:

  1. /** 
  2. 支付回調(diào)處理者 
  3. */ 
  4. PayCallBackController implements ApplicationContextAware { 
  5.      private ApplicationContext applicationContext; 
  6.  
  7.     //如果想獲取applicationContext需要實現(xiàn)ApplicationContextAware接口,Spring容器會回調(diào)setApplicationContext方法將applicationContext注入進(jìn)來 
  8.     @Override 
  9.     public void setApplicationContext(ApplicationContext applicationContext) 
  10.             throws BeansException { 
  11.         this.applicationContext = applicationContext; 
  12.     } 
  13.      @RequestMapping(value = "/pay/callback.do"
  14.      public View callback(HttpServletRequest request){ 
  15.         if(paySuccess(request){ 
  16.             //構(gòu)造支付成功事件 
  17.             PaySuccessEvent event = buildPaySuccessEvent(...); 
  18.             //通過applicationContext發(fā)布事件,從而達(dá)到通知觀察者的目的 
  19.             this.applicationContext.publishEvent(event); 
  20.         }  
  21.     } 
  22. /** 
  23.  * 語音播報處理者 
  24.  * 
  25.  */ 
  26. public class VoiceBroadcastHandler implements ApplicationListener<PaySuccessEvent>{ 
  27.     @Override 
  28.     public void onApplicationEvent(PaySuccessEvent event) { 
  29.         //語音播報邏輯 
  30.     } 
  31.  
  32. //其他處理者的邏輯類似 

總結(jié):觀察者模式將被觀察者和觀察者之間做了解耦,觀察者存在與否不會影響被觀察者的現(xiàn)有邏輯。

裝飾器設(shè)計模式

模式定義

裝飾器用來包裝原有的類,在對使用者透明的情況下做功能的增強(qiáng),比如 Java 中的 BufferedInputStream 可以對其包裝的 InputStream 做增強(qiáng),從而提供緩沖功能。

使用場景

希望對原有類的功能做增強(qiáng),但又不希望增加過多子類時,可以使用裝飾器模式來達(dá)到同樣的效果。

實踐經(jīng)驗

筆者之前在推動整個公司接入 trace 體系,因此也提供了一些工具來解決 trace 的自動織入和上下文的自動傳遞。

為了支持線程間的上下文傳遞,我增加了 TraceRunnableWrapper 這個裝飾類,從而起到將父線程的上下文透傳到子線程中,對使用者完全透明。

代碼如下:

  1. /** 
  2. 可以自動攜帶trace上下文的Runnable裝飾器 
  3. */ 
  4. public class TraceRunnableWrapper implements Runnable{ 
  5.     //被包裝的目標(biāo)對象 
  6.     private Runnable task; 
  7.     private Span parentSpan = null
  8.  
  9.     public TraceRunnableWrapper(Runnable task) { 
  10.         //1.獲取當(dāng)前線程的上下文(因為new的時候還沒有發(fā)生線程切換,所以需要在這里將上下文獲?。?nbsp;
  11.         //對這塊代碼感興趣的可以查看opentracing API 
  12.         io.opentracing.Scope currentScope = GlobalTracer.get().scopeManager().active(); 
  13.         //2.保存父上下文 
  14.         parentSpan = currentScope.span(); 
  15.         this.task = task; 
  16.     } 
  17.  
  18.     @Override 
  19.     public void run() { 
  20.         //run的時候?qū)⒏妇€程的上下文綁定到當(dāng)前線程 
  21.         io.opentracing.Scope scope = GlobalTracer.get().scopeManager().activate(parentSpan,false); 
  22.         task.run(); 
  23.     } 
  24.  
  25. //使用者 
  26. new Thread(new Runnable(){run(...)}).start()替換為new TraceRunnableWrapper(new Runnable(){run(...)}).start() 

總結(jié):使用裝飾器模式做了功能的增強(qiáng),對使用者來說只需要做簡單的組合就能繼續(xù)使用原功能。

外觀設(shè)計模式

模式定義

何為外觀,就是對外提供一個統(tǒng)一的入口:

  • 一是可以影藏系統(tǒng)內(nèi)部的細(xì)節(jié)。
  • 二是可以降低使用者的復(fù)雜度。

比如 SpringMVC 中的 DispaterServlet,所有的 Controller 都是通過 DispaterServlet 統(tǒng)一暴露。

使用場景

降低使用者的復(fù)雜度,簡化客戶端的接入成本。

實踐經(jīng)驗

筆者所在的公司對外提供了一些開放能力給第三方 ISV,比如設(shè)備管控、統(tǒng)一支付、對賬單下載等能力。

由于分屬于不同的團(tuán)隊,所以對外提供的接口形式各異,初期還好,接口不多,ISV 也能接受,但是后期接口多了 ISV 就開始抱怨接入成本太高。

為了解決這一問題,我們在開放接口前面加了一層前端控制器 GatewayController,其實就是我們后來開放平臺的雛形。

GatewayController 對外統(tǒng)一暴露一個接口 gateway.do,將對外接口的請求參數(shù)和響應(yīng)參數(shù)統(tǒng)一在 GatewayController 做收斂,GatewayController 往后端服務(wù)路由時也采用統(tǒng)一接口。

改造前后對比如下圖:

大概代碼如下:

  1. 使用者: 
  2. HttpClient.doPost("/gateway.do","{'method':'trade.create','sign':'wxxaaa','timestamp':'15311111111'},'bizContent':'業(yè)務(wù)參數(shù)'"
  3.  
  4. GatewayController: 
  5. @RequestMapping("/gateway.do"
  6. JSON gateway(HttpServletRequest req){ 
  7.    //1.組裝開放請求 
  8.    OpenRequest openRequest = buildOpenRequest(req); 
  9.  
  10.    OpenResponse openResponse = null
  11.    //2.請求路由 
  12.    if("trade.create".equals(openRequest.getMethod()){ 
  13.        //proxy to trade service by dubbo 
  14.        openResponse = TradeFacade.execute(genericParam); 
  15.    } else if("iot.message.push".equals(openRequest.getMethod()){ 
  16.        //proxy to iot service by httpclient 
  17.         openResponse = HttpClient.doPost('http://iot.service/generic/execute'genericParam); 
  18.    } 
  19.  
  20.    if(openResponse.isSuccess()){ 
  21.         return {"code":"10000","bizContent":openResponse.getResult()}; 
  22.    }else
  23.         return {"code":"20000","bizCode":openResponse.getCode()}; 
  24.    } 
  25.  
  26.  

總結(jié):采用外觀模式屏蔽了系統(tǒng)內(nèi)部的一些細(xì)節(jié),降低了使用者的接入成本。

就拿 GatewayController 來說,ISV 的鑒權(quán),接口的驗簽等重復(fù)工作統(tǒng)一由它實現(xiàn)。

ISV 對接不同的接口只需要關(guān)心一套接口協(xié)議接口,由 GatewayController 這一層做了收斂。

作者:踩刀詩人

編輯:陶家龍

出處:https://urlify.cn/J3mAna

 

責(zé)任編輯:武曉燕 來源: urlify.cn
相關(guān)推薦

2020-08-20 08:39:54

CTO代碼數(shù)據(jù)

2017-09-08 12:15:54

Python代碼Pythonic

2021-07-20 06:37:33

CTO代碼程序員

2022-03-23 08:01:04

Python語言代碼

2020-07-21 08:06:05

日志

2010-02-02 10:08:19

CTO

2018-09-30 09:36:58

CTO代碼程序員

2021-10-18 08:15:12

CTO代碼裁員

2024-08-12 00:00:00

NPMCTOJavaScrip

2013-05-02 09:36:44

代碼項目

2020-12-07 08:43:55

代碼軟件開發(fā)

2024-10-29 09:25:00

2021-09-09 18:12:22

內(nèi)存分段式網(wǎng)絡(luò)

2021-03-01 08:57:41

CTO代碼架構(gòu)師

2020-03-20 08:00:32

代碼程序員追求

2018-02-25 11:00:34

代碼開發(fā)程序員

2012-07-11 11:05:16

編程

2017-03-21 15:01:47

BAT算法數(shù)據(jù)

2011-09-08 14:24:57

51cto 51CTO

2020-01-07 14:44:09

GitHub代碼開發(fā)者
點贊
收藏

51CTO技術(shù)棧公眾號