This is the first day of my participation in the More text Challenge. For details, see more text Challenge

The business scenario

Recently, I received a small demand from the company to expand the existing application rules for trial users. Our scenario might look something like this:

If (overseas user) {return false; } if (brush user) {return false; } the if (not paying customers & no longer service time)} {return false if (turn to introduce the user | | subscribers | | push users) {return true; }Copy the code

According to the above conditions, we can come to the conclusion that:

Our main process is based on the and or or relationship. If there is a mismatch, in fact, we do not need to execute the subsequent process, that is, we need to have a short circuit function. For the current situation, if I based on the original, as long as a little attention to solve the needs of the problem is not very big, but said the maintainability is very poor.

I decided to refactor this part after some further weighing. Rule executor For this requirement, I first combed the general design of our rule executor, and then I designed a V1 version to share with you. If you also have such a case, you can share your comments with me. The following part is mainly about the design and implementation process and code. Design of rule actuators

Abstraction of rules and implementation of rules

@data public class RuleDto {private String address; private int age; } 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(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(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; if (nationalityRuleDto.getNationality().startsWith(MATCH_NATIONALITY_START)) { return true; } return false; Public class RuleConstant {public static final String MATCH_ADDRESS_START= "MATCH_ADDRESS_START "; Public static final String MATCH_NATIONALITY_START= "China "; }Copy the code

Actuator build

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: // System.out.println("execute key = "+ 1); if (! and(dto, ruleList)) { return false; } break; Case 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 the relationship fails to match once, return false return false; } // Return true; } private boolean or(RuleDto dto, List<BaseRule> ruleList) { for (BaseRule rule : ruleList) { boolean execute = rule.execute(dto); If (execute) {// return true if the relationship matches one; } // Return false; return false; }}Copy the code

Call to the actuator

Public class RuleServiceTest {@org.junit.test public void execute() {public class RuleServiceTest {@org.junit.test Simple, each rule can be independent, rules, data, executor is broken down, the caller is relatively neat // disadvantages: data depends on the common transport object 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); Dtos. Elegantly-named setName (" zhang "); Dtos. SetAddress (" Beijing "); Dtos. SetSubject (" mathematics "); 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); }}Copy the code

conclusion

The advantages and disadvantages of rule actuators

  • Advantages: Relatively simple, each rule can be independent, rules, data, actuator is split out, the caller is more orderly; I define the convert method in the Rule template class to convert the parameters so that I can extend the scenario data that a particular Rule needs.

  • Disadvantages: The upper and lower rules are data dependent. It is not reasonable to directly modify the common transport object DTO, so it is recommended to build the data in advance.