需求描述
在一個交易系統中,由於歷史原因,存在2類商戶。一類屬於自營商戶,存儲於A表(Id,商戶號,名稱)中,一類屬於外部平臺商戶,存儲於B表(Id,平臺號,商戶號,名稱)中
目前需要篩查系統中所有的商戶,同時再對這些數據做一些處理,篩查模式有3種:
- 1.所有的商戶:包括 自營商戶(A表)和 平臺商戶(B表)以及再做一些處理工作;
- 2.根據平臺號:全部的自營商戶(A表)或者 平臺商戶(B表)中指定的平臺,以及再做一些處理工作;
(其中平臺號爲0,爲自營商戶) - 3.根據商戶號:篩查所有的商戶,包括 自營商戶(A表)和 平臺商戶(B表),以及再做一些處理工作。
tip:(- 除以上3種外,未來可能的還會補充一些篩查方法,據說產品經理現在還沒想好,畢竟是惹不起的人,隨時加需求…what…·~)
以上需求不算太複雜,根據其描述,很快就能定義出大致的邏輯處理實現。
public void check(int type, String pId, String mId) {
if (type == 1) {
//1.所有的商戶
List allA = tableAService.selectAll();//自營
doSomething(allA);
List allB = tableBService.selectAll();//平臺
doSomething(allB);
} else if (type == 2) {
//2.根據平臺
if ("0".equals(pId)) {
List allA = tableAService.selectAll();//自營
doSomething(allA);
} else {
List pidB = tableBService.selectPid(pId);//平臺
doSomething(pidB);
}
} else if (type == 3) {
//3.根據商戶號
List midA = tableAService.selectMid(mId);//自營
doSomething(midA);
List midB = tableBService.selectMid(mId);//平臺
doSomething(midB);
}
// ...
}
我想說的是,功能實現是沒有問題的,有沒有更好的方法呢。比如萬一哪天,產品經理說,我們需要第4種模式,同時根據平臺和商戶號查詢呢?
我們可能就需要再新增一個if條件分支…
public void check(int type, String pId, String mId) {
if (type == 1) {
//1.所有的商戶
List allA = tableAService.selectAll();
doSomething(allA);
List allB = tableBService.selectAll();
doSomething(allB);
} else if (type == 2) {
//2.根據平臺
if ("0".equals(pId)) {
List allA = tableAService.selectAll();
doSomething(allA);
} else {
List pidB = tableBService.selectPid(pId);
doSomething(pidB);
}
} else if (type == 3) {
//3.根據商戶號
List midA = tableAService.selectMid(mId);
doSomething(midA);
List midB = tableBService.selectMid(mId);
doSomething(midB);
} else if (type == 4){
//4.fuck xxxx
}
// ...
}
做過開發的朋友都會明白了,以上代碼要維護的話,先要能夠找到對應的代碼在if分支塊中,而且if的縮進代碼塊,也容易讓人看花眼,萬一沒對齊就慘了.。
這裏,我將介紹一種方法,使用策略模式改造if分支過多的方法
爲了結合項目代碼,使用Spring框架,實現類的管理,採用自定義註解與枚舉類結合,簡化代碼。
首先定義我們的type枚舉類
CheckTypeEnum.java
public enum CheckTypeEnum {
All(1, "所有商戶"),
PID(2, "根據平臺"),
MID(3, "根據商戶號"),
;
private Integer type;
private String desc;
CheckTypeEnum(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
//getter and setter ...
}
自定義註解:
CheckTypeAnno.java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CheckTypeAnno {
CheckTypeEnum value();
}
定義我們的策略接口
UserCheckStrategy.java
public interface UserCheckStrategy {
void check(int type, String pId, String mId);
}
第一種類型的檢查方式實現類
UserCheckAll.java
@Service
@CheckTypeAnno(CheckTypeEnum.All)
public class UserCheckAll implements UserCheckStrategy {
@Resource
TableAService tableAService;
@Resource
TableBService tableBService;
@Override
public void check(int type, String pId, String mId) {
//1.所有的商戶
List allA = tableAService.selectAll();
doSomething(allA);
List allB = tableBService.selectAll();
doSomething(allB);
}
private void doSomething(List list) {
// doSomething
}
}
第二種類型的檢查方式實現類
UserCheckPid.java
@Service
@CheckTypeAnno(CheckTypeEnum.PID)
public class UserCheckPid implements UserCheckStrategy {
@Resource
TableAService tableAService;
@Resource
TableBService tableBService;
@Override
public void check(int type, String pId, String mId) {
//2.根據平臺
if ("0".equals(pId)) {
List allA = tableAService.selectAll();
doSomething(allA);
} else {
List pidB = tableBService.selectPid(pId);
doSomething(pidB);
}
}
private void doSomething(List list) {
// doSomething
}
}
第三種類型的檢查方式實現類
UserCheckPid.java
@Service
@CheckTypeAnno(CheckTypeEnum.MID)
public class UserCheckMid implements UserCheckStrategy {
@Resource
TableAService tableAService;
@Resource
TableBService tableBService;
@Override
public void check(int type, String pId, String mId) {
//3.根據商戶號
List midA = tableAService.selectMid(mId);
doSomething(midA);
List midB = tableBService.selectMid(mId);
doSomething(midB);
}
private void doSomething(List list) {
// doSomething
}
}
策略上下文對象
UserCheckStrategyContext.java
@Service
public class UserCheckStrategyContext {
@Resource
List<UserCheckStrategy> userCheckStrategies;
void check(int type, String pId, String mId) {
for (UserCheckStrategy userCheckStrategy : userCheckStrategies) {
CheckTypeAnno checkTypeAnno = userCheckStrategy.getClass().getAnnotation(CheckTypeAnno.class);
if (checkTypeAnno != null && checkTypeAnno.value().getType() == type) {
userCheckStrategy.check(type,pId,mId);
return;
}
}
}
}
這裏將userCheckStrategies的所有實現類交由Spring管理,通過匹配實現類上標註的註解,來決定使用具體哪種策略實現類進行商戶數據篩查。
最終改造之後的客戶端調用方法爲:
UserService.java
...
@Resource
UserCheckStrategyContext userCheckStrategyContext;
/**
* 處理商戶
*
* @param type 1.所有的商戶 2.根據平臺號 3.根據商戶號
* @param pId 平臺號
* @param mId 商戶號
*/
public void check(int type, String pId, String mId) {
/*if (type == 1) {
//1.所有的商戶
List allA = tableAService.selectAll();
doSomething(allA);
List allB = tableBService.selectAll();
doSomething(allB);
} else if (type == 2) {
//2.根據平臺
if ("0".equals(pId)) {
List allA = tableAService.selectAll();
doSomething(allA);
} else {
List pidB = tableBService.selectPid(pId);
doSomething(pidB);
}
} else if (type == 3) {
//3.根據商戶號
List midA = tableAService.selectMid(mId);
doSomething(midA);
List midB = tableBService.selectMid(mId);
doSomething(midB);
} else if (type == 4){
//4.fuck xxxx
}*/
// ...
// 使用策略模式後,方法調用爲:
userCheckStrategyContext.check(type,pId,mId);
}
改造後,每種情況的檢查,將是一個具體的枚舉值以及對應的具體策略接口實現類。
需求變更
這個時候,出現了第四種情況,只需要對應的維護一個接口實現類以及枚舉變量值了。
如下
CheckTypeEnum.java
public enum CheckTypeEnum {
All(1, "所有商戶"),
PID(2, "根據平臺"),
MID(3, "根據商戶號"),
MORE(4, "更多"),
;
private Integer type;
private String desc;
CheckTypeEnum(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
}
UserCheckMore.java
@Service
@CheckTypeAnno(CheckTypeEnum.MORE)
public class UserCheckMore implements UserCheckStrategy {
@Override
public void check(int type, String pId, String mId) {
//4.更多處理方式
// ...
}
}
其他原有的方式與結構不需要任何變化!
演示項目的文件結構:
總結
通過將控制分支與具體的處理邏輯分離,解決了if分支過多的問題。這裏實際上是利用了Java接口的多態特性,實現了分支選擇。
具體選擇哪個分支,由上下文對象根據其實現類上標註的註解進行選擇。
這樣,即便出現了篩查模式類型type爲:4,5,6,7…
再多分支,也能輕鬆應對!
面向對象的程序設計中,有一條開閉原則,它規定“軟件中的對象(類,模塊,函數等等)應該對於擴展是開放的,但是對於修改是封閉的”,這意味着一個實體是允許在不改變它的源代碼的前提下變更它的行爲。
代碼的修改是不可避免的,但是通過結構的改造,能夠最小限度的減少其修改,也更方便維護。
歡迎看過的朋友底下留言~