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>

        【91期】面試官:Spring 用了哪些設計模式?說三種即可

        共 7017字,需瀏覽 15分鐘

         ·

        2020-11-18 09:35

        程序員的成長之路
        互聯(lián)網(wǎng)/程序員/技術/資料共享?
        關注


        閱讀本文大概需要 7 分鐘。

        來自:網(wǎng)絡

        關于設計模式,如果使用得當,將會使我們的代碼更加簡潔,并且更具擴展性。本文主要講解Spring中如何使用策略模式,工廠方法模式以及Builder模式。

        1. 策略模式

        關于策略模式的使用方式,在Spring中其實比較簡單,從本質上講,策略模式就是一個接口下有多個實現(xiàn)類,而每種實現(xiàn)類會處理某一種情況。
        我們以發(fā)獎勵為例進行講解,比如我們在抽獎系統(tǒng)中,有多種獎勵方式可供選擇,比如積分,虛擬幣和現(xiàn)金等。
        在存儲時,我們必然會使用一個類似于type的字段用于表征這幾種發(fā)放獎勵的,那么這里我們就可以使用多態(tài)的方式進行獎勵的發(fā)放。比如我們抽象出一個PrizeSender的接口,其聲明如下:

        public?interface?PrizeSender?{

        ??/**
        ???*?用于判斷當前實例是否支持當前獎勵的發(fā)放
        ???*/

        ??boolean?support(SendPrizeRequest?request);

        ??/**
        ???*?發(fā)放獎勵
        ???*/

        ??void?sendPrize(SendPrizeRequest?request);

        }

        該接口中主要有兩個方法:support()和sendPrize(),其中support()方法主要用于判斷各個子類是否支持當前類型數(shù)據(jù)的處理,而sendPrize()則主要是用于進行具體的業(yè)務處理的,比如這里獎勵的發(fā)放。
        下面就是我們?nèi)N不同類型的獎勵發(fā)放的具體代碼:

        //?積分發(fā)放
        @Component
        public?class?PointSender?implements?PrizeSender?{

        ??@Override
        ??public?boolean?support(SendPrizeRequest?request)?{
        ????return?request.getPrizeType()?==?PrizeTypeEnum.POINT;
        ??}

        ??@Override
        ??public?void?sendPrize(SendPrizeRequest?request)?{
        ????System.out.println("發(fā)放積分");
        ??}
        }

        //?虛擬幣發(fā)放
        @Component
        public?class?VirtualCurrencySender?implements?PrizeSender?{

        ??@Override
        ??public?boolean?support(SendPrizeRequest?request)?{
        ????return?PrizeTypeEnum.VIRTUAL_CURRENCY?==?request.getPrizeType();
        ??}

        ??@Override
        ??public?void?sendPrize(SendPrizeRequest?request)?{
        ????System.out.println("發(fā)放虛擬幣");
        ??}
        }

        //?現(xiàn)金發(fā)放
        @Component
        public?class?CashSender?implements?PrizeSender?{

        ??@Override
        ??public?boolean?support(SendPrizeRequest?request)?{
        ????return?PrizeTypeEnum.CASH?==?request.getPrizeType();
        ??}

        ??@Override
        ??public?void?sendPrize(SendPrizeRequest?request)?{
        ????System.out.println("發(fā)放現(xiàn)金");
        ??}
        }
        這里可以看到,在每種子類型中,我們只需要在support()方法中通過request的某個參數(shù)來控制當前request是否是當前實例能夠處理的類型,如果是,則外層的控制邏輯就會將request交給當前實例進行處理。
        關于這個類的設計,有幾個點需要注意:
        • 使用@Component注解對當前類進行標注,將其聲明為Spring容器所管理的一個bean;
        • 聲明一個返回boolean值的類似于support()的方法,通過這個方法來控制當前實例是否為處理目標request的實例;
        • 聲明一個類似于sendPrize()的方法用于處理業(yè)務邏輯,當然根據(jù)各個業(yè)務的不同聲明的方法名肯定是不同的,這里只是一個對統(tǒng)一的業(yè)務處理的抽象;
        • 無論是support()方法還是sendPrize()方法,都需要傳一個對象進行,而不是簡簡單單的基本類型的變量,這樣做的好處是后續(xù)如果要在Request中新增字段,那么就不需要修改接口的定義和已經(jīng)實現(xiàn)的各個子類的邏輯;

        2. 工廠方法模式

        上面我們講解了如何使用Spring來聲明一個策略模式,那么如何為不同的業(yè)務邏輯來注入不同的bean呢,或者說外層的控制邏輯是什么樣的,這里我們就可以使用工廠方法模式了。
        所謂的工廠方法模式,就是定義一個工廠方法,通過傳入的參數(shù),返回某個實例,然后通過該實例來處理后續(xù)的業(yè)務邏輯。一般的,工廠方法的返回值類型是一個接口類型,而選擇具體子類實例的邏輯則封裝到了工廠方法中了。
        通過這種方式,來將外層調用邏輯與具體的子類的獲取邏輯進行分離。如下圖展示了工廠方法模式的一個示意圖:
        可以看到,工廠方法將具體實例的選擇進行了封裝,而客戶端,也就是我們的調用方只需要調用工廠的具體方法獲取到具體的事例即可,而不需要管具體的實例實現(xiàn)是什么。
        上面我們講解了Spring中是如何使用策略模式聲明處理邏輯的,而沒有講如何選擇具體的策略,這里我們就可以使用工廠方法模式。
        如下是我們聲明的一個PrizeSenderFactory:

        @Component
        public?class?PrizeSenderFactory?{

        ??@Autowired
        ??private?List?prizeSenders;

        ??public?PrizeSender?getPrizeSender(SendPrizeRequest?request)?{
        ????for?(PrizeSender?prizeSender?:?prizeSenders)?{
        ??????if?(prizeSender.support(request))?{
        ????????return?prizeSender;
        ??????}
        ????}

        ????throw?new?UnsupportedOperationException("unsupported?request:?"?+?request);
        ??}
        }

        這里我們聲明一個了一個工廠方法getPrizeSender(),其入?yún)⒕褪荢endPrizeRequest,而返回值是某個實現(xiàn)了PrizeSender接口的實例,可以看到,通過這種方式,我們將具體的選擇方式下移到了具體的子類中的,因為當前實現(xiàn)了PrizeSender的bean是否支持當前request的處理,是由具體的子類實現(xiàn)的。
        在該工廠方法中,我們也沒有任何與具體子類相關的邏輯,也就是說,該類實際上是可以動態(tài)檢測新加入的子類實例的。
        這主要是通過Spring的自動注入來實現(xiàn)的,主要是因為我們這里注入的是一個List,也就是說,如果有新的PrizeSender的子類實例,只要其是Spring所管理的,那么都會被注入到這里來。
        下面就是我們編寫的一段用于測試的代碼來模擬調用方的調用:

        @Service
        public?class?ApplicationService?{

        ??@Autowired
        ??private?PrizeSenderFactory?prizeSenderFactory;

        ??public?void?mockedClient()?{
        ????SendPrizeRequest?request?=?new?SendPrizeRequest();
        ????request.setPrizeType(PrizeTypeEnum.POINT);??//?這里的request一般是根據(jù)數(shù)據(jù)庫或外部調用來生成的
        ????PrizeSender?prizeSender?=?prizeSenderFactory.getPrizeSender(request);
        ????prizeSender.sendPrize(request);
        ??}
        }

        在客戶端代碼中,首先通過PrizeSenderFactory獲取一個PrizeSender實例,然后通過其sendPrize()方法發(fā)放具體的獎勵,通過這種方式,將具體的獎勵發(fā)放邏輯與客戶端調用進行了解耦。
        而且根據(jù)前面的講解,我們也知道,如果新增了一種獎勵方式,我們只需要聲明一個新的實現(xiàn)了PrizeSender的bean即可,而不需要對現(xiàn)有代碼進行任何修改。

        3. Builder模式

        關于Builder模式,我想使用過lombok的同學肯定會說builder模式非常的簡單,只需要在某個bean上使用@Builder注解進行聲明即可,lombok可以自動幫我們將其聲明為一個Builder的bean。關于這種使用方式,本人不置可否,不過就我的理解,這里主要有兩個點我們需要理解:
        1、Builder模式就其名稱而言,是一個構建者,我更傾向于將其理解為通過一定的參數(shù),通過一定的業(yè)務邏輯來最終生成某個對象。如果僅僅只是使用lombok的這種方式,其本質上也還是創(chuàng)建了一個簡單的bean,這個與通過getter和setter方式構建一個bean是沒有什么大的區(qū)別的;
        2、在Spring框架中,使用設計模式最大的問題在于如果在各個模式bean中能夠注入Spring的bean,如果能夠注入,那么將大大的擴展其使用方式。因為我們就可以真的實現(xiàn)通過傳入的簡單的幾個參數(shù),然后結合Spring注入的bean進行一定的處理后,以構造出我們所需要的某個bean。顯然,這是lombok所無法實現(xiàn)的;
        關于Builder模式,我們可以以前面獎勵發(fā)放的SendPrizeRequest的構造為例進行講解。在構造request對象的時候,必然是通過前臺傳如的某些參數(shù)來經(jīng)過一定的處理,最后生成一個request對象。那么我們就可以使用Builder模式來構建一個SendPrizeRequest。
        這里假設根據(jù)前臺調用,我們能夠獲取到prizeId和userId,那么我們就可以創(chuàng)建一個如下的SendPrizeRequest:

        public?class?SendPrizeRequest?{

        ??private?final?PrizeTypeEnum?prizeType;
        ??private?final?int?amount;
        ??private?final?String?userId;

        ??public?SendPrizeRequest(PrizeTypeEnum?prizeType,?int?amount,?String?userId)?{
        ????this.prizeType?=?prizeType;
        ????this.amount?=?amount;
        ????this.userId?=?userId;
        ??}

        ??@Component
        ??@Scope("prototype")
        ??public?static?class?Builder?{

        ????@Autowired
        ????PrizeService?prizeService;

        ????private?int?prizeId;
        ????private?String?userId;

        ????public?Builder?prizeId(int?prizeId)?{
        ??????this.prizeId?=?prizeId;
        ??????return?this;
        ????}

        ????public?Builder?userId(String?userId)?{
        ??????this.userId?=?userId;
        ??????return?this;
        ????}

        ????public?SendPrizeRequest?build()?{
        ??????Prize?prize?=?prizeService.findById(prizeId);
        ??????return?new?SendPrizeRequest(prize.getPrizeType(),?prize.getAmount(),?userId);
        ????}
        ??}

        ??public?PrizeTypeEnum?getPrizeType()?{
        ????return?prizeType;
        ??}

        ??public?int?getAmount()?{
        ????return?amount;
        ??}

        ??public?String?getUserId()?{
        ????return?userId;
        ??}
        }

        這里就是使用Spring維護一個Builder模式的示例,具體的 維護方式就是在Builder類上使用@Component和@Scope注解來標注該Builder類,這樣我們就可以在Builder類中注入我們所需要的實例來進行一定的業(yè)務處理了。關于該模式,這里有幾點需要說明:
        • 在Builder類上必須使用@Scope注解來標注該實例為prototype類型,因為很明顯,我們這里的Builder實例是有狀態(tài)的,無法被多線程共享;
        • 在Builder.build()方法中,我們可以通過傳入的參數(shù)和注入的bean來進行一定的業(yè)務處理,從而得到構建一個SendPrizeRequest所需要的參數(shù);
        • Builder類必須使用static修飾,因為在Java中,如果內(nèi)部類不用static修飾,那么該類的實例必須依賴于外部類的一個實例,而我們這里本質上是希望通過內(nèi)部類實例來構建外部類實例,也就是說內(nèi)部類實例存在的時候,外部類實例是還不存在的,因而這里必須使用static修飾;
        • 根據(jù)標準的Builder模式的使用方式,外部類的各個參數(shù)都必須使用final修飾,然后只需要為其聲明getter方法即可。
        上面我們展示了如何使用Spring的方式來聲明一個Builder模式的類,那么我們該如何進行使用呢,如下是我們的一個使用示例:

        @Service
        public?class?ApplicationService?{

        ??@Autowired
        ??private?PrizeSenderFactory?prizeSenderFactory;

        ??@Autowired
        ??private?ApplicationContext?context;

        ??public?void?mockedClient()?{
        ????SendPrizeRequest?request?=?newPrizeSendRequestBuilder()
        ????????.prizeId(1)
        ????????.userId("u4352234")
        ????????.build();

        ????PrizeSender?prizeSender?=?prizeSenderFactory.getPrizeSender(request);
        ????prizeSender.sendPrize(request);
        ??}

        ??public?Builder?newPrizeSendRequestBuilder()?{
        ????return?context.getBean(Builder.class);
        ??}
        }

        上述代碼中,我們主要要看一下newPrizeSendRequestBuilder()方法,在Spring中,如果一個類是多例類型,也即使用@Scope("prototype")進行了標注,那么每次獲取該bean的時候就必須使用ApplicationContext.getBean()方法獲取一個新的實例,至于具體的原因,讀者可查閱相關文檔。
        我們這里就是通過一個單獨的方法來創(chuàng)建一個Builder對象,然后通過流式來為其設置prizeId和userId等參數(shù),最后通過build()方法構建得到了一個SendPrizeRequest實例,通過該實例來進行后續(xù)的獎勵發(fā)放。

        4. 小結

        本文主要通過一個獎勵發(fā)放的示例來對Spring中如何使用工廠方法模式,策略模式和Builder模式的方式進行講解,并且著重強調了實現(xiàn)各個模式時我們所需要注意的點。
        推薦閱讀:

        【90期】面試官:說一下使用 Redis 實現(xiàn)大規(guī)模的帖子瀏覽計數(shù)的思路

        【89期】面試官 5 連問一個 TCP 連接可以發(fā)多少個 HTTP 請求?

        【88期】面試官問:你能說說 Spring 中,接口的bean是如何注入的嗎?

        5T技術資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機,樹莓派,等等。在公眾號內(nèi)回復「2048」,即可免費獲?。?!

        微信掃描二維碼,關注我的公眾號

        朕已閱?

        瀏覽 31
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        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>
            中文字幕人妻一区二区三区在线视频 | 美女黄大片 | 国产欧美熟妇另类久久久 | 免费国产成人看片在线 | 一级AAA片特爽高潮网站 | 成人无码免费福利视频 | 日本级婬乱片A片AAA毛片地址 | 天天综合久久综合 | 被舔的好爽 | 中文字幕在线一区 |