策略模式的學(xué)習(xí)

1. 問(wèn)題來(lái)源

在年初看某業(yè)務(wù)線(xiàn)清結(jié)算部分代碼時(shí),遇到這么一串代碼,大致如下:


//如果商戶(hù)文件到達(dá)刮刑,使用ftp下載文件
if (map.get(SystemConstant.SYSTEM_TYPE).equals("2")) {
    try{
            ... ...

        ApacheFTPUtil plugins=new ApacheFTPUtil();
        plugins.execute(paramMap);//下載文件路徑信息在paramMap中

        businessLogger1.info(... + "FTP文件下載結(jié)束");
    }catch(Exception e){
        ... ...
    }
}
//如果文件下載到文件核心完成
if (result.getIsSuccess() == TaskResult.TASK_EXEC_SUCCESS) {
    businessLogger1.info(... + "文件到達(dá)檢測(cè)啟動(dòng)");

    BaseComponentPlugins plugins = new FileArrive();//調(diào)用文件級(jí)實(shí)現(xiàn)
    result = new TaskResult();
    plugins.execute(map, result);

    businessLogger1.info(... + "文件到達(dá)檢測(cè)結(jié)束");
    
    //文件級(jí)檢測(cè)完成且沒(méi)有問(wèn)題味赃,開(kāi)始進(jìn)行記錄級(jí)檢查
    if (result.getIsSuccess() == TaskResult.TASK_EXEC_SUCCESS) {
        businessLogger1.info(... + "文件校驗(yàn)啟動(dòng)");
        result = new TaskResult();

        plugins = new FileValid();
        plugins.execute(map, result);//調(diào)用記錄級(jí)實(shí)現(xiàn)

        businessLogger1.info(... + "文件校驗(yàn)結(jié)束");

        //文件記錄入庫(kù)
        if (result.getIsSuccess() == TaskResult.TASK_EXEC_SUCCESS) {
            businessLogger1.info(... + "異步入庫(kù)啟動(dòng)");
            result = new TaskResult();

            plugins = new FileEnterDB();
            plugins.execute(map, result);//調(diào)用異步入庫(kù)實(shí)現(xiàn)

            businessLogger1.info(... + "異步入庫(kù)結(jié)束");

            //入庫(kù)HDFS
            if (result.getIsSuccess() == TaskResult.TASK_EXEC_SUCCESS) {
                businessLogger1.info(map, "入庫(kù)HDFS開(kāi)始");
                result = new TaskResult();
                
                plugins = new HdfsLoad();
                plugins.execute(map, result);//調(diào)用入庫(kù)HDFS實(shí)現(xiàn)

                businessLogger1.info(map, "入庫(kù)HDFS結(jié)束");
            }else{
                ... ...//入庫(kù)HDFS異常
            }           
        }else{
            ... ...//文件記錄入庫(kù)異常
        }
    }else{
        ... ...//文件記錄級(jí)入庫(kù)/異步校驗(yàn)異常
    }
}else{
    ... ...//文件級(jí)校驗(yàn)異常
}

額外說(shuō)明的是:

  • 由于清算步驟前后因果關(guān)系,必須前一步執(zhí)行成功才能繼續(xù)下一步乾胶,所以代碼結(jié)構(gòu)是這樣多個(gè)if嵌套的樣子
  • 哪一步失敗就需要立即停止并反饋到日志或者數(shù)據(jù)庫(kù),各個(gè)else的代碼也就是處理這些異常的,此處也沒(méi)有列出飒筑。

代碼中多次使用到了這樣的結(jié)構(gòu):

BaseComponentPlugins plugins = new ... ...
plugins.execute(map, result);

可能熟悉策略模式的人早就明白這是怎么回事了,但是我剛開(kāi)始看得時(shí)候就只是覺(jué)得绽昏,簡(jiǎn)潔明了协屡、維護(hù)和可讀性都很高,再就是感嘆接口原來(lái)是這么用的全谤。

2. 策略模式是什么肤晓?

最近在看資料的時(shí)候,偶然看到幾篇介紹策略模式的文章,才慢慢理解了补憾÷眩回顧到這段代碼的時(shí)候,也才漸漸明白這就是策略模式的實(shí)現(xiàn)盈匾。
策略模式的理解可以參見(jiàn)這篇文章腾务,例子生動(dòng)形象:【行為型模式十五】策略模式(Strategy)
我的理解就是把一個(gè)個(gè)具體的算法和使用算法的類(lèi)是進(jìn)行解耦,比如結(jié)算業(yè)務(wù)的例子被拆分成:

  • FTP下載文件
  • 文件級(jí)校驗(yàn)
  • 記錄及校驗(yàn)
  • 異步入庫(kù)
  • 入庫(kù)HDFS

每種操作都單獨(dú)形成一個(gè)單獨(dú)的算法類(lèi)削饵,它們共同實(shí)現(xiàn)同一個(gè)接口岩瘦、接收相同參數(shù)結(jié)構(gòu)的參數(shù),而具體使用哪一個(gè)由使用算法的類(lèi)來(lái)選擇窿撬。
接口定義如下:


package com.xxxx.commonbase.plugins;

import java.util.Map;

import com.xxxx.platform.bean.TaskResult;

public interface BaseComponentPlugins {
    abstract int execute(Map<String, String> map, TaskResult result);
}

再通過(guò)類(lèi)圖關(guān)系可以更清楚的看到算法實(shí)現(xiàn)與策略接口的關(guān)系:

預(yù)處理

上圖的關(guān)系可以看到启昧,這種處理方式很接近策略模式了,但是PretreatmentDeal類(lèi)中并沒(méi)有持有策略(也就是BaseComponentPlugins接口)的引用劈伴,為了完成結(jié)算它還是通過(guò)先調(diào)用FileArrive密末、FileValid... ... 這樣一步步完成的,只是實(shí)例化這些對(duì)象使用BaseComponentPlugins引用而已宰啦,如之前所述:

BaseComponentPlugins plugins = new ... ...
plugins.execute(map, result);

而真正的策略模式苏遥,除了策略接口規(guī)范一系列具體的策略算法所應(yīng)該完成的事;還需要上下文context類(lèi)要持有策略接口赡模,當(dāng)調(diào)用這個(gè)上下文類(lèi)的對(duì)象時(shí)田炭,會(huì)決定使用哪種策略實(shí)現(xiàn)。
如果真的要以策略模式的實(shí)現(xiàn)漓柑,這個(gè)PretreatmentDeal類(lèi)就必須先有一個(gè)策略的成員變量教硫,修改后的類(lèi)關(guān)系圖應(yīng)該如下:


使用策略模式的預(yù)處理

當(dāng)然原本PretreatmentDeal類(lèi)的各種if條件需要抽取形成新的調(diào)用類(lèi),由這個(gè)調(diào)用類(lèi)來(lái)判斷每一步的結(jié)果并決定是否更換策略,PretreatmentDealContext只是簡(jiǎn)單調(diào)用excetu()方法并返回結(jié)果辆布。

3.業(yè)務(wù)線(xiàn)強(qiáng)相關(guān)的數(shù)據(jù)源切換

最近集中管控臺(tái)需要管理使用多個(gè)數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)的選擇只與業(yè)務(wù)線(xiàn)相關(guān)聯(lián).在最開(kāi)始的時(shí)候我能想到的代碼可能也就是類(lèi)似下面的if判斷形式了.


//依據(jù)產(chǎn)品和業(yè)務(wù)線(xiàn)取不同的數(shù)據(jù)庫(kù)資源
if (paramMap.getProduct.equals(SystemConstant.PRODUCT_A)) {
    if (paramMap.getBussinessLine.equals(SystemConstant.BUSSINESS_LINE_A)) {
        ... ...//取A業(yè)務(wù)線(xiàn)對(duì)應(yīng)的數(shù)據(jù)庫(kù)
    }else if(paramMap.getBussinessLine.equals(SystemConstant.BUSSINESS_LINE_B)) {
        ... ...//取B業(yè)務(wù)線(xiàn)對(duì)應(yīng)的數(shù)據(jù)庫(kù)
    }else if(paramMap.getBussinessLine.equals(SystemConstant.BUSSINESS_LINE_C)) {
        ... ... //取C業(yè)務(wù)線(xiàn)對(duì)應(yīng)的數(shù)據(jù)庫(kù)
    }
}else if(paramMap.getProduct.equals(SystemConstant.PRODUCT_B) {
    if (paramMap.getBussinessLine.equals(SystemConstant.BUSSINESS_LINE_A)) {
        ... ...//取A業(yè)務(wù)線(xiàn)對(duì)應(yīng)的數(shù)據(jù)庫(kù)
    }else if(paramMap.getBussinessLine.equals(SystemConstant.BUSSINESS_LINE_B)) {
        ... ...//取B業(yè)務(wù)線(xiàn)對(duì)應(yīng)的數(shù)據(jù)庫(kù)
    }   
}

這樣的寫(xiě)法缺點(diǎn)在策略模式的講解文章已經(jīng)談了無(wú)數(shù)遍了,所以這個(gè)可以考慮修改下代碼,抽象出數(shù)據(jù)源的獲取方式/算法瞬矩,新增一個(gè)contex類(lèi)拿到獲取數(shù)據(jù)源的接口引用。
這樣锋玲,當(dāng)service層代碼獲取數(shù)據(jù)源的時(shí)候景用,只需要依據(jù)業(yè)務(wù)現(xiàn)選擇哪種策略/數(shù)據(jù)源就可以了,大致的思路如下:

使用策略模式的多數(shù)據(jù)源獲取

剩下的工作就是service層依據(jù)controller層的參數(shù),做出選擇:

  • 依據(jù)產(chǎn)品product選擇使用哪種策略(即哪種獲取數(shù)據(jù)庫(kù)資源的方式,比如oracle惭蹂、mysql)伞插,
  • 依據(jù)具體業(yè)務(wù)現(xiàn)選擇哪個(gè)數(shù)據(jù)源(比如同一個(gè)數(shù)ip下不同數(shù)據(jù)庫(kù)用戶(hù)名管理的表等等)
    這樣,可以避免大量的判斷條件盾碗。上下文類(lèi)DataSourceContext代碼寫(xiě)法較為固定媚污,類(lèi)似這樣:

/**
* 數(shù)據(jù)源管理,完成向調(diào)用者返回匹配的數(shù)據(jù)庫(kù)資源
*/
public class DataSourceContext {
  /**
   * 持有一個(gè)具體的策略對(duì)象(數(shù)據(jù)庫(kù)資源接口)
   */
  private AccessDataSource AccessDataSource = null;
  
  /**
   * 構(gòu)造方法廷雅,傳入一個(gè)具體業(yè)務(wù)線(xiàn)數(shù)據(jù)庫(kù)資源對(duì)象
   */
  public DataSourceContext(Strategy aStrategy){
      this.strategy = aStrategy;
  }  
  
  /**
   * 獲取數(shù)據(jù)庫(kù)資源
   * @param bussinessChannel業(yè)務(wù)線(xiàn)編碼
   * @return 業(yè)務(wù)線(xiàn)關(guān)聯(lián)的數(shù)據(jù)庫(kù)資源
   */
  public double getDataSource(String bussinessChannel){
      return this.strategy.getDataSource(bussinessChannel);
  }
}

這么做就具備了很強(qiáng)的靈活性耗美,比如配置不同類(lèi)型數(shù)據(jù)庫(kù)京髓、同一個(gè)數(shù)據(jù)庫(kù)多個(gè)數(shù)據(jù)庫(kù)用戶(hù)的管理;尤其是新增一個(gè)業(yè)務(wù)現(xiàn)商架,只需新增業(yè)務(wù)線(xiàn)

4.一點(diǎn)感受

凡事有利有弊,策略模式要求調(diào)用者必須清楚地知道每種策略實(shí)現(xiàn)堰怨,這樣才能在選擇的時(shí)候傳遞給context類(lèi)具體的策略。比如這個(gè)數(shù)據(jù)源的例子調(diào)用類(lèi)就必須的寫(xiě)成:


public class ProductAUserServiceImpl {
    
    //選擇并創(chuàng)建需要使用的數(shù)據(jù)源甸私,需要顯示聲明使用哪一種
    AccessDataSource strategy = new ProductADataSource();
    
    //創(chuàng)建上下文對(duì)象
    DataSourceContext context =  new DataSourceContext(strategy);
    
    /**
    * 依據(jù)業(yè)務(wù)線(xiàn)bussinessChannel拿到具體mybatis dao
    */
    public UserMapperDao UserMapperDao(String bussinessChannel){
        return context.strategy.getDataSource(bussinessChannel);
    }
    
    /**
    * 具體查詢(xún)實(shí)現(xiàn)诚些,供給controller層使用
    */
    public List<User> queryByName(String bussinessID, String name) {
        this.UserMapperDao(bussinessID).queryByName(name);
    }
    
}

也有說(shuō)法可以調(diào)用者不需要知道具體哪些策略,完全有context類(lèi)依據(jù)參數(shù)控制皇型,沒(méi)有在繼續(xù)看,但感覺(jué)如果處理不好砸烦、context類(lèi)又要出現(xiàn)一堆if判斷弃鸦,到時(shí)又得想辦法處理掉這些判斷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末幢痘,一起剝皮案震驚了整個(gè)濱河市唬格,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颜说,老刑警劉巖购岗,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異门粪,居然都是意外死亡喊积,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)玄妈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)乾吻,“玉大人,你說(shuō)我怎么就攤上這事拟蜻∫锴” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵酝锅,是天一觀(guān)的道長(zhǎng)诡必。 經(jīng)常有香客問(wèn)我,道長(zhǎng)搔扁,這世上最難降的妖魔是什么爸舒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮阁谆,結(jié)果婚禮上碳抄,老公的妹妹穿的比我還像新娘。我一直安慰自己场绿,他們只是感情好剖效,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般璧尸。 火紅的嫁衣襯著肌膚如雪咒林。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天爷光,我揣著相機(jī)與錄音垫竞,去河邊找鬼。 笑死蛀序,一個(gè)胖子當(dāng)著我的面吹牛欢瞪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播徐裸,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼遣鼓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了重贺?” 一聲冷哼從身側(cè)響起骑祟,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎气笙,沒(méi)想到半個(gè)月后次企,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潜圃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年缸棵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秉犹。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛉谜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崇堵,到底是詐尸還是另有隱情型诚,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布鸳劳,位于F島的核電站狰贯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赏廓。R本人自食惡果不足惜涵紊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幔摸。 院中可真熱鬧摸柄,春花似錦、人聲如沸既忆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至跃脊,卻和暖如春宇挫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酪术。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工器瘪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绘雁。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓橡疼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咧七。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衰齐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容