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)系:
上圖的關(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)該如下:
當(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ù)源就可以了,大致的思路如下:
剩下的工作就是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í)又得想辦法處理掉這些判斷。