當前位置: 妍妍網 > 碼農

還在寫大量 if 判斷?試試規則執行器,真香!

2024-04-25碼農

最近公司有個小需求,需要對之前已有的試用使用者申請規則進行拓展。我們的場景大概如下所示:

if (是否海外使用者) {
returnfalse;
}
if (刷單使用者) {
returnfalse;
}
if (未付費使用者 && 不再服務時段) {
returnfalse
}
if (轉介紹使用者 || 付費使用者 || 內推使用者) {
returntrue;
}

按照上述的條件我們可以得出的結論是:

  1. 咱們的的主要流程主要是基於 and 或者 or 的關系。

  2. 如果有一個不匹配的話,其實咱們後續的流程是不用執行的,就是需要具備一個 短路 的功能。

  3. 對於目前的現狀來說,我如果在原有的基礎上來該,只要稍微註意一下解決需求不是很大的問題,但是說後面可維護性非常差。

後面進過權衡過後,我還是決定將這個部份進行重構一下。

規則執行器

針對這個需求,我首先梳理了一下咱們規則執行器大概的設計, 然後我設計了一個 V1 版本和大家一起分享一下,如果大家也有這樣的 case 可以給我分享留言,下面部份主要是設計和實作的流程和 code。

規則執行器的設計

規則處理邏輯最佳化

對於規則的抽象並實作規則

// 業務數據
@Data
public classRuleDto{
private String address;
privateint age;
}
// 規則抽象
publicinterfaceBaseRule{
booleanexecute(RuleDto dto);
}
// 規則樣版
publicabstract classAbstractRuleimplementsBaseRule{
protected <T> convert(RuleDto dto){
return (T) dto;
}
@Override
publicbooleanexecute(RuleDto dto){
return executeRule(convert(dto));
}
protected <T> booleanexecuteRule(T t){
returntrue;
}
}
// 具體規則- 例子1
public classAddressRuleextendsAbstractRule{
@Override
publicbooleanexecute(RuleDto dto){
System.out.println("AddressRule invoke!");
if (dto.getAddress().startsWith(MATCH_ADDRESS_START)) {
returntrue;
}
returnfalse;
}
}
// 具體規則- 例子2
public classNationalityRuleextendsAbstractRule{
@Override
protected <T> convert(RuleDto dto){
NationalityRuleDto nationalityRuleDto = new NationalityRuleDto();
if (dto.getAddress().startsWith(MATCH_ADDRESS_START)) {
nationalityRuleDto.setNationality(MATCH_NATIONALITY_START);
}
return (T) nationalityRuleDto;
}

@Override
protected <T> booleanexecuteRule(T t){
System.out.println("NationalityRule invoke!");
NationalityRuleDto nationalityRuleDto = (NationalityRuleDto) t;
if (nationalityRuleDto.getNationality().startsWith(MATCH_NATIONALITY_START)) {
returntrue;
}
returnfalse;
}
}
// 常量定義
public classRuleConstant{
publicstaticfinal String MATCH_ADDRESS_START= "北京";
publicstaticfinal String MATCH_NATIONALITY_START= "中國";
}










執行器構建

public classRuleService{
private Map<Integer, List<BaseRule>> hashMap = new HashMap<>();
privatestaticfinalint AND = 1;
privatestaticfinalint OR = 0;
publicstatic RuleService create(){
returnnew RuleService();
}

public RuleService and(List<BaseRule> ruleList){
hashMap.put(AND, ruleList);
returnthis;
}
public RuleService or(List<BaseRule> ruleList){
hashMap.put(OR, ruleList);
returnthis;
}
publicbooleanexecute(RuleDto dto){
for (Map.Entry<Integer, List<BaseRule>> item : hashMap.entrySet()) {
List<BaseRule> ruleList = item.getValue();
switch (item.getKey()) {
case AND:
// 如果是 and 關系,同步執行
System.out.println("execute key = " + 1);
if (!and(dto, ruleList)) {
returnfalse;
}
break;
case OR:
// 如果是 or 關系,並列執行
System.out.println("execute key = " + 0);
if (!or(dto, ruleList)) {
returnfalse;
}
break;
default:
break;
}
}
returntrue;
}
privatebooleanand(RuleDto dto, List<BaseRule> ruleList){
for (BaseRule rule : ruleList) {
boolean execute = rule.execute(dto);
if (!execute) {
// and 關系匹配失敗一次,返回 false
returnfalse;
}
}
// and 關系全部匹配成功,返回 true
returntrue;
}
privatebooleanor(RuleDto dto, List<BaseRule> ruleList){
for (BaseRule rule : ruleList) {
boolean execute = rule.execute(dto);
if (execute) {
// or 關系匹配到一個就返回 true
returntrue;
}
}
// or 關系一個都匹配不到就返回 false
returnfalse;
}
}





執行器的呼叫

public classRuleServiceTest{
@org.junit.Test
publicvoidexecute()
{
//規則執行器
//優點:比較簡單,每個規則可以獨立,將規則,數據,執行器拆分出來,呼叫方比較規整
//缺點:數據依賴公共傳輸物件 dto
//1. 定義規則 init rule
AgeRule ageRule = new AgeRule();
NameRule nameRule = new NameRule();
NationalityRule nationalityRule = new NationalityRule();
AddressRule addressRule = new AddressRule();
SubjectRule subjectRule = new SubjectRule();
//2. 構造需要的數據 create dto
RuleDto dto = new RuleDto();
dto.setAge(5);
dto.setName("張三");
dto.setAddress("北京");
dto.setSubject("數學");;
//3. 透過以鏈式呼叫構建和執行 rule execute
boolean ruleResult = RuleService
.create()
.and(Arrays.asList(nationalityRule, nameRule, addressRule))
.or(Arrays.asList(ageRule, subjectRule))
.execute(dto);
System.out.println("this student rule execute result :" + ruleResult);
}
}


總結

規則執行器的優點和缺點

  • 優點:

    1. 比較簡單,每個規則可以獨立,將規則,數據,執行器拆分出來,呼叫方比較規整;

    2. 我在 Rule 樣版類中定義 convert 方法做參數的轉換這樣可以能夠,為特定 rule 需要的場景數據提供拓展。

  • 缺點:上下 rule 有數據依賴性,如果直接修改公共傳輸物件 dto 這樣設計不是很合理,建議提前構建數據。

  • 來源:juejin.cn/post/6951764927958745124

    >>

    END

    精品資料,超贊福利,免費領

    微信掃碼/長按辨識 添加【技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

    👇👇

    👇點選"閱讀原文",獲取更多資料(持續更新中