使用規則執行器代替 if else 判斷


業務場景

近日有個需求,需要對之前已有的用戶申請規則進行拓展。場景大概如下所示:

if (是否海外用戶) {
 return false;
}

if (刷單用戶) {
  return false;
}

if (未付費用戶 && 不再服務時段) {
  return false
}

if (轉介紹用戶 || 付費用戶 || 內推用戶) {
  return true;
}

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

  • 流程主要是基於 and 或者 or 的關系。
  • 如果有一個不匹配的話,后續的流程是不用執行的,就是需要具備一個短路的功能。
  • 對於目前的現狀來說,如果在原有的基礎上來改,只要稍微注意一下解決需求不是很大的問題,但是說后面可維護性非常差。

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

規則執行器

規則執行器設計

規則的抽象和實現

/**
 * 基礎規則業務數據
 * @author LiuHuan
 * @date 2021/4/23 10:24 上午
 */
@Data
public class RuleDTO {

    private String address;

    private int age;

}

/**
 * 具體業務數據
 * @author LiuHuan
 * @date 2021/4/23 10:47 上午
 */
@Data
public class NationalityRuleDTO extends RuleDTO{

    private String nationality;

}

/**
 * 規則接口
 * @author LiuHuan
 * @date 2021/4/23 10:33 上午
 */
public interface Rule {

    boolean execute(RuleDTO dto);

}

/**
 * 基礎規則
 * @author LiuHuan
 * @date 2021/4/23 10:35 上午
 */
public abstract class BaseRule implements Rule{

    public static final String MATCH_ADDRESS_START= "北京";

    public static final String MATCH_NATIONALITY_START= "中國";

    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;
    }

}

/**
 * 地址規則
 * @author LiuHuan
 * @date 2021/4/23 10:38 上午
 */
public class AddressRule extends BaseRule{

    @Override
    public boolean execute(RuleDTO dto) {
        System.out.println("AddressRule invoke!");
        return dto.getAddress().startsWith(MATCH_ADDRESS_START);
    }

}

/**
 * 國家規則
 * @author LiuHuan
 * @date 2021/4/23 10:39 上午
 */
public class NationalityRule extends BaseRule{

    @Override
    protected <T> 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> boolean executeRule(T t) {
        System.out.println("NationalityRule invoke!");
        NationalityRuleDTO nationalityRuleDto = (NationalityRuleDTO) t;
        return nationalityRuleDto.getNationality().startsWith(MATCH_NATIONALITY_START);
    }

}

執行器

/**
 * 規則執行
 * @author LiuHuan
 * @date 2021/4/23 10:41 上午
 */
public class RuleManage {

    private Map<Integer, List<Rule>> hashMap = new HashMap<>();
    private static final int AND = 1;
    private static final int OR = 0;

    public static RuleManage create() {
        return new RuleManage();
    }


    public RuleManage and(List<Rule> ruleList) {
        hashMap.put(AND, ruleList);
        return this;
    }

    public RuleManage or(List<Rule> ruleList) {
        hashMap.put(OR, ruleList);
        return this;
    }

    public boolean execute(RuleDTO dto) {
        for (Map.Entry<Integer, List<Rule>> item : hashMap.entrySet()) {
            List<Rule> 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<Rule> ruleList) {
        for (Rule rule : ruleList) {
            boolean execute = rule.execute(dto);
            if (!execute) {
                // and 關系匹配失敗一次,返回 false
                return false;
            }
        }
        // and 關系全部匹配成功,返回 true
        return true;
    }

    private boolean or(RuleDTO dto, List<Rule> ruleList) {
        for (Rule rule : ruleList) {
            boolean execute = rule.execute(dto);
            if (execute) {
                // or 關系匹配到一個就返回 true
                return true;
            }
        }
        // or 關系一個都匹配不到就返回 false
        return false;
    }

}

測試

/**
 * @author LiuHuan
 * @date 2021/4/23 10:49 上午
 */
public class RuleManageTest {

    @Test
    public void execute() {
        //規則執行器
        //優點:比較簡單,每個規則可以獨立,將規則,數據,執行器拆分出來,調用方比較規整
        //缺點:數據依賴公共傳輸對象 dto

        //1. 定義規則  init rule
        NationalityRule nationalityRule = new NationalityRule();
        AddressRule addressRule = new AddressRule();

        //2. 構造需要的數據 create dto
        RuleDTO dto = new RuleDTO();
        dto.setAge(5);
        dto.setAddress("北京");

        //3. 通過以鏈式調用構建和執行 rule execute
        boolean ruleResult = RuleManage
            .create()
            .and(Arrays.asList(nationalityRule, addressRule))
            //.or(Arrays.asList(addressRule, nationalityRule))
            .execute(dto);
        System.out.println("this student rule execute result :" + ruleResult);
    }

}

總結

優點

  • 比較簡單,每個規則可以獨立,將規則,數據,執行器拆分出來,調用方比較規整
  • Rule 模板類中定義 convert 方法做參數的轉換這樣可以能夠,為特定 rule 需要的場景數據提供拓展

缺點

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM