1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 5597字,需瀏覽 12分鐘

         ·

        2021-06-12 11:14

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

        前言

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

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

        舉幾個(gè)我見(jiàn)過(guò)的極端的例子:

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

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

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

        4. ……

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

        原因

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

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

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

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

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

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

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

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

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

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

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

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

        舉個(gè)栗子

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

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

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

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

        ...
        復(fù)制代碼

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

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

        還記得這句話嗎?

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

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

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

        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ù)制代碼

        然后再來(lái)一個(gè)委托模式的線程池,將 execute方法重寫(xiě)。把線程池中原本的 Runnable 對(duì)象包裝為剛才的 MDCDelegateRunnable,在創(chuàng)建時(shí),將 traceId 通過(guò)構(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ù)制代碼

        搞定,來(lái)測(cè)試一下:

        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ù)制代碼

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

        JDK 里的委托模式

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

        比如我只是需要一個(gè)異步并且獲取返回的操作,直接 new 線程 start 的話,獲取返回值又不太方便,如果通過(guò)線程池的 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ù)制代碼

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

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

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

        // 委托類(lèi)里,在 finalize 被委托的線程池對(duì)象的 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ù)制代碼

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

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

        總結(jié)

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

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

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

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


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



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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            午夜成人一区二区三区 | 国产麻豆天美果冻无码视频 | 深夜福利在线播放 | 在线观看成人网站 | 99精品少妇 | 一女两男做爰3p文 | 精品国产精品一区二区夜夜嗨 | 男女无遮挡xx00动态图120秒 | 亚洲精品18禁 | 91黑人玩弄极品人妻 |