<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么總是用不好設(shè)計(jì)模式?

          共 5597字,需瀏覽 12分鐘

           ·

          2021-06-12 11:14

          先點(diǎn)贊再看,養(yǎng)成好習(xí)慣

          前言

          經(jīng)??吹揭恍┰O(shè)計(jì)模式的文章,寫了很多內(nèi)容,也舉了一些很“生動(dòng)形象”的例子。

          但是可能和《Head First 設(shè)計(jì)模式》會(huì)有一樣的問題:看完了,我會(huì)了,但是好像用不上?或者是硬套設(shè)計(jì)模式。

          舉幾個(gè)我見過的極端的例子:

          1. 倆字段,也要來個(gè) Builder

          2. 3個(gè) if,提個(gè)策略模式

          3. 5行代碼還很簡單的初始化,也要弄個(gè) Factory

          4. ……

          至于為什么會(huì)出現(xiàn)這種問題……我聊聊我的看法

          原因

          大多數(shù)的研發(fā)人員,做的工作都是業(yè)務(wù)功能開發(fā),也就是常說的 CRUD。只是不同的業(yè)務(wù)場景,CRUD的復(fù)雜度不同而已。

          可是對于業(yè)務(wù)代碼來說,很多情況下不太好套用設(shè)計(jì)模式,或者說沒法很好的應(yīng)用設(shè)計(jì)模式。

          平時(shí)看到的最多的是策略模式的文章吧,為什么呢?

          我猜是因?yàn)檫@個(gè)最好寫,應(yīng)用在業(yè)務(wù)代碼里比較簡單;隨便一個(gè)稍微復(fù)雜點(diǎn)的場景,就可以套用一下策略模式,把多個(gè) if 拆分到多個(gè)類里。

          的確,業(yè)務(wù)代碼里適當(dāng)?shù)氖褂貌呗阅J娇梢越档蛷?fù)雜度;但就算用也得住一個(gè)度,不要把各種業(yè)務(wù)里的 if 都換成策略模式了,不然代碼會(huì)炸的……

          之前見過一個(gè)項(xiàng)目,雖然是內(nèi)部xx系統(tǒng),但那個(gè)研發(fā)小哥可能是走火入魔了。抽了 80 多個(gè)策略類出來,這八十多個(gè)類里又分為十幾個(gè)組,每組七八個(gè)策略類,但每個(gè)類里的代碼,也不過十幾二十行,而且還有重復(fù)代碼。

          我當(dāng)時(shí)問了他一句:你在養(yǎng)蠱嗎?

          像這個(gè)研發(fā)小哥就是一個(gè)反例,濫用設(shè)計(jì)模式,過分的將各種分支代碼全部套進(jìn)設(shè)計(jì)模式了。不過我猜想他可能是為了學(xué)習(xí)吧,學(xué)以致用……

          其他的委托代理狀態(tài)之類的模式,想應(yīng)用在業(yè)務(wù)代碼里,就比較費(fèi)勁了,因?yàn)闆]有那么多合適的場景。但策略模式則不同,有 if 的地方都可以嘗試套一下……

          可是設(shè)計(jì)模式是用來解決問題,降低/轉(zhuǎn)移復(fù)雜度的,而不是增加復(fù)雜度。

          非業(yè)務(wù)代碼里的設(shè)計(jì)模式

          跳出業(yè)務(wù)代碼來,甚至說跳出純業(yè)務(wù)代碼來之后,想應(yīng)用設(shè)計(jì)模式就比較簡單了,甚至不需要你硬套,遇到問題時(shí)就自然的會(huì)想到用設(shè)計(jì)模式來解決。

          舉個(gè)栗子

          系統(tǒng)里一般需要一個(gè) traceId/requestId 來將整個(gè)鏈路串起來,配合日志打印或者集中式的APM抽取。

          就拿單體應(yīng)用來說,一般用日志框架的 MDC 來綁定這個(gè) traceId。在 Filter 或者 一些 AOP 里,給 MDC 一個(gè) traceID,那么整個(gè)調(diào)用鏈路都可以用這一個(gè) ID,打印日志時(shí)就可以根據(jù) traceId 區(qū)分不同請求了,就像這樣:

          2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 請求第0步
          2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 請求第1步
          2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 請求第2步

          2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 請求第0步
          2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 請求第1步
          2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 請求第2步

          ...
          復(fù)制代碼

          通過 000/111 這個(gè) traceId 就可以區(qū)分是哪個(gè)請求。

          可 MDC 是通過 ThreadLocal 進(jìn)行存儲(chǔ)數(shù)據(jù)的,ThreadLocal 畢竟是和線程綁定的。如果鏈路中使用了線程池處理,那可怎么辦?線程池里子線程打印日志的時(shí)候,MDC 可獲取不到主線程的 traceId,但對于這個(gè)請求來說,主子線程都是一個(gè)鏈路……

          還記得這句話嗎?

          計(jì)算機(jī)科學(xué)領(lǐng)域的任何問題都可以通過增加一個(gè)間接的中間層來解決”

          這里借助委托模式,來增加一個(gè)中間層,問題就很好解決了。

          既然是主子線程的數(shù)據(jù)傳遞問題,那么只需要在創(chuàng)建子線程的時(shí)候,從主線程里將 MDC 里的 traceId 拿出來,傳遞給新建的子線程就可以了,就像這樣:

          public class MDCDelegateRunnable implements Runnable{

          private Runnable target;

          private String traceId;

          public MDCDelegateRunnable(Runnable target, String traceId) {
          this.target = target;
          this.traceId = traceId;
          }

          @Override
          public void run()
          {
          MDC.put("traceId",traceId);
          target.run();
          MDC.remove("traceId");
          }
          }
          復(fù)制代碼

          然后再來一個(gè)委托模式的線程池,將 execute方法重寫。把線程池中原本的 Runnable 對象包裝為剛才的 MDCDelegateRunnable,在創(chuàng)建時(shí),將 traceId 通過構(gòu)造參數(shù)傳遞

          public class MDCDelegateExecutorService extends AbstractExecutorService {

          public MDCDelegateExecutorService(AbstractExecutorService target) {
          this.target = target;
          }

          private AbstractExecutorService target;

          @Override
          public void shutdown() {
          target.shutdown();
          }

          //...

          @Override
          public void execute(@NotNull Runnable command) {
          target.execute(new MDCDelegateRunnable(command, MDC.get("traceId")));
          }

          }
          復(fù)制代碼

          搞定,來測試一下:

          public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
          MDC.put("traceId","111");
          new MDCDelegateExecutorService((AbstractExecutorService) Executors.newFixedThreadPool(5)).execute(new Runnable() {
          @Override
          public void run() {
          System.out.println("runnable: "+MDC.get("traceId"));
          }
          });
          Future<String> future = new MDCDelegateExecutorService((AbstractExecutorService) Executors.newFixedThreadPool(5)).submit(new Callable<String>() {
          @Override
          public String call() throws Exception {
          return MDC.get("traceId");
          }
          });

          System.out.println("callable: "+future.get());

          System.in.read();
          }

          //output
          runnable: 111
          callable: 111
          復(fù)制代碼

          完美,本來麻煩的 traceId 傳遞問題,現(xiàn)在通過一個(gè)簡單的委托模式就解決了。不用修改調(diào)用方代碼,也沒有破壞線程池的代碼。

          JDK 里的委托模式

          還記得Executors#newSingleThreadExecutor這個(gè)單線程線程池的創(chuàng)建方法吧,那什么情況下需要單線程的線程池呢?

          比如我只是需要一個(gè)異步并且獲取返回的操作,直接 new 線程 start 的話,獲取返回值又不太方便,如果通過線程池的 Callable/Runnable + Future 就方便了:

          ExecutorService executorService = Executors.newSingleThreadExecutor();

          Future<String> future = executorService.submit(new Callable<String>() {
          @Override
          public String call() throws Exception {
          // do sth...
          return data;
          }
          });

          String data = future.get();

          executorService.shutdown();
          復(fù)制代碼

          對于單線程異步的場景來說,甚至都不需要維護(hù)一個(gè)單例的線程池,每次 new/shutdown 也可以。可是我都單線程了,每次還要 shutdown 是不是有點(diǎn)不太方便,萬一哪里忘了 shutdown 了,那可不完蛋了……

          JDK 的設(shè)計(jì)者也想到了這個(gè)問題,而且他們也已經(jīng)解決了這個(gè)問題。和上面的例子類似,利用一個(gè)簡單的委托模式,就可以完美解決這個(gè)問題:

          public static ExecutorService newSingleThreadExecutor() {
          //創(chuàng)建 FinalizableDelegatedExecutorService 委托類
          return new FinalizableDelegatedExecutorService
          (new ThreadPoolExecutor(1, 1,
          0L, TimeUnit.MILLISECONDS,
          new LinkedBlockingQueue<Runnable>()));
          }

          // 委托類里,在 finalize 被委托的線程池對象的 shutdown方法,自動(dòng)關(guān)閉線程池
          static class FinalizableDelegatedExecutorService
          extends DelegatedExecutorService
          {
          FinalizableDelegatedExecutorService(ExecutorService executor) {
          super(executor);
          }
          protected void finalize() {
          super.shutdown();
          }
          }

          // 公共的抽象委托線程池……
          static class DelegatedExecutorService extends AbstractExecutorService {
          private final ExecutorService e;

          DelegatedExecutorService(ExecutorService executor) { e = executor; }

          public void execute(Runnable command) { e.execute(command); }

          public void shutdown() { e.shutdown(); }
          //...
          }
          復(fù)制代碼

          這樣一來,在使用 newSingleThreadExecutor的時(shí)候,甚至都不需要顯示 shutdown 了……

          注意:雖然JDK 幫我們關(guān)了……但還是建議手動(dòng) shutdown,把 JDK 的這個(gè)機(jī)制當(dāng)做一個(gè)防呆設(shè)計(jì),萬一忘了 JDK 還能自動(dòng)關(guān)閉,避免泄露的問題

          總結(jié)

          結(jié)合上面兩個(gè)例子來看,一旦跳出業(yè)務(wù)代碼的范圍,應(yīng)用設(shè)計(jì)模式是不是變得很簡單?甚至都不需要硬往設(shè)計(jì)模式上套,遇到問題你自然會(huì)想到用設(shè)計(jì)模式來解決問題,而不是用設(shè)計(jì)模式在代碼里養(yǎng)蠱……

          在純業(yè)務(wù)代碼中,適當(dāng)?shù)牟鸱郑3执a整潔可讀性強(qiáng)帶來的收益,遠(yuǎn)比套一堆設(shè)計(jì)模式要強(qiáng)

          重復(fù)一遍:設(shè)計(jì)模式是用來解決問題,降低/轉(zhuǎn)移復(fù)雜度的,而不是增加復(fù)雜度

          以上僅個(gè)人看法,如有不同意見歡迎評論區(qū)留言


          作者:空無
          鏈接:https://juejin.cn/post/6972372366131200036
          來源:掘金
          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。



          瀏覽 36
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲欧洲视频在线 | 久久熟女网 | 巨大乳人妻中文字幕 | 午夜福利一区 | 欧美+国产+无码+麻豆 |