業務場景
有一個小需求需要對之前已有的試用用戶申請規則進行拓展。我們的場景大概如下所示
if (是否海外用戶) { return false; } if (刷單用戶) { return false; } if (未付費用戶 && 不再服務時段) { return false } if (轉介紹用戶 || 付費用戶 || 內推用戶) { return true; }
按照上述的條件我們可以得出的結論是:
- 咱們的的主要流程主要是基於 and 或者 or 的關係。
- 如果有一個不匹配的話,其實咱們後續的流程是不用執行的,就是需要具備一個短路的功能。
- 對於目前的現狀來說,我如果在原有的基礎上來改,只要稍微注意一下解決需求不是很大的問題,但是說後面可維護性非常差。
後面進過權衡過後,我還是決定將這個部分進行重構一下。
規則執行器
針對這個需求,我首先梳理了一下咱們規則執行器大概的設計, 然後我設計了一個 V1 版本和大家一起分享一下,如果大家也有這樣的 case 可以給我分享留言,下面部分主要是設計和實現的流程和 code.
規則執行器的設計
對於規則的抽象並實現規則
業務數據
@EqualsAndHashCode @Data @Builder @AllArgsConstructor @NoArgsConstructor @ToString public class RuleDTO { private String address; private Integer age; private String name; private String subject; }
@EqualsAndHashCode @Data @SuperBuilder @AllArgsConstructor @NoArgsConstructor @ToString public class SubjectDTO extends RuleDTO { private String sort; }
@EqualsAndHashCode @Data @SuperBuilder @AllArgsConstructor @NoArgsConstructor @ToString public class NationalityRuleDTO extends RuleDTO { private String nationality; }
public class RuleConstant { public static final String MATCH_ADDRESS_START = "北京"; public static final String MATCH_NATIONALITY_START = "中國"; public static final String MATCH_SUBJECT_MATH = "數學"; public static final String MATCH_SUBJECT_SCIENCE = "理科"; }
規則抽象
public interface BaseRule { boolean execute(RuleDTO dto); }
規則模板
public abstract class AbstractRule implements BaseRule { protected <T> T convert(RuleDTO dto) { return (T) dto; } @Override public boolean execute(RuleDTO dto) { return executeRule(convert(dto)); } protected <T> boolean executeRule(T t) { return true; } }
具體規則
實例一
public class AddressRule extends AbstractRule { @Override public boolean execute(RuleDTO dto) { System.out.println("AddressRule invoke!"); if (dto.getAddress().startsWith(RuleConstant.MATCH_ADDRESS_START)) { return true; } return false; } }
實例二
public class NationalityRule extends AbstractRule { @Override protected <T> T convert(RuleDTO dto) { NationalityRuleDTO nationalityRuleDto = new NationalityRuleDTO(); if (dto.getAddress().startsWith(RuleConstant.MATCH_ADDRESS_START)) { nationalityRuleDto.setNationality(RuleConstant.MATCH_NATIONALITY_START); } return (T) nationalityRuleDto; } @Override protected <T> boolean executeRule(T t) { System.out.println("NationalityRule invoke!"); NationalityRuleDTO nationalityRuleDto = (NationalityRuleDTO) t; if (StringUtils.isNotBlank(nationalityRuleDto.getNationality()) && nationalityRuleDto.getNationality() .startsWith(RuleConstant.MATCH_NATIONALITY_START)) { return true; } return false; } }
實例三
public class AgeRule extends AbstractRule { @Override protected <T> boolean executeRule(T t) { System.out.println("AgeRule invoke!"); RuleDTO ruleDTO = (RuleDTO) t; if (ruleDTO.getAge() > 18) { return true; } return false; } }
實例四
public class NameRule extends AbstractRule { @Override protected <T> boolean executeRule(T t) { System.out.println("NameRule invoke!"); RuleDTO ruleDTO = (RuleDTO) t; if (ruleDTO.getName().contains("張")) { return true; } return false; } }
實例五
public class SubjectRule extends AbstractRule { @Override protected <T> T convert(RuleDTO dto) { SubjectDTO subjectDTO = new SubjectDTO(); if (StringUtils.equals(dto.getSubject(), RuleConstant.MATCH_SUBJECT_MATH)) { subjectDTO.setSort(RuleConstant.MATCH_SUBJECT_SCIENCE); } return (T) subjectDTO; } @Override protected <T> boolean executeRule(T t) { SubjectDTO subjectDTO = (SubjectDTO) t; System.out.println("SubjectRule invoke!"); if (StringUtils.equals(subjectDTO.getSort(), RuleConstant.MATCH_SUBJECT_SCIENCE)) { return true; } return false; } }
執行器構建
public class RuleService { private Map<Integer, List<BaseRule>> hashMap = new HashMap<>(); private static final int AND = 1; private static final int OR = 0; public static RuleService create() { return new RuleService(); } public RuleService and(List<BaseRule> ruleList) { hashMap.put(AND, ruleList); return this; } public RuleService or(List<BaseRule> ruleList) { hashMap.put(OR, ruleList); return this; } public boolean execute(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)) { return false; } break; case OR: // 如果是 or 關係,並行執行 System.out.println("execute key = " + 0); if (!or(dto, ruleList)) { return false; } break; default: break; } } return true; } private boolean and(RuleDTO dto, List<BaseRule> ruleList) { for (BaseRule rule : ruleList) { boolean execute = rule.execute(dto); if (!execute) { // and 關係匹配失敗一次,返回 false return false; } } // and 關係全部匹配成功,返回 true return true; } private boolean or(RuleDTO dto, List<BaseRule> ruleList) { for (BaseRule rule : ruleList) { boolean execute = rule.execute(dto); if (execute) { // or 關係匹配到一個就返回 true return true; } } // or 關係一個都匹配不到就返回 false return false; } }
執行器調用
public class RuleServiceTest { @Test public void execute() { //規則執行器 //優點:比較簡單,每個規則可以獨立,將規則,數據,執行器拆分出來,調用方比較規整 //缺點:數據依賴公共傳輸對象 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.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); } }
執行結果
總結
規則執行器的優點和缺點
優點:
- 比較簡單,每個規則可以獨立,將規則,數據,執行器拆分出來,調用方比較規整;
- 我在 Rule 模板類中定義 convert 方法做參數的轉換這樣可以能夠,爲特定 rule 需要的場景數據提供拓展。
缺點:
- 上下 rule 有數據依賴性,如果直接修改公共傳輸對象 dto 這樣設計不是很合理,建議提前構建數據。
原文參考公衆號【Java知音】