Design and Implementation of a Java Rule Engine with AND/OR Logic
This article explains how to design and refactor a Java rule engine that supports short‑circuit AND/OR logic, demonstrates the abstraction of rules, shows concrete implementations, and provides a chainable service for executing combined rule sets with example code.
The author presents a practical case of extending an existing user‑application rule system by building a reusable rule engine in Java, emphasizing the need for short‑circuit evaluation of AND/OR conditions and highlighting maintainability concerns of ad‑hoc if‑else chains.
Initial business logic is illustrated with a simple if‑else snippet:
if (是否海外用户) {
return false;
}
if (刷单用户) {
return false;
}
if (未付费用户 && 不再服务时段) {
return false;
}
if (转介绍用户 || 付费用户 || 内推用户) {
return true;
}The engine’s design introduces a data transfer object ( RuleDto ), a BaseRule interface, an abstract AbstractRule template, concrete rule classes ( AddressRule , NationalityRule ), and a constants holder ( RuleConstant ).
// 业务数据
@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 convert(RuleDto dto) {
return (T) dto;
}
@Override
public boolean execute(RuleDto dto) {
return executeRule(convert(dto));
}
protected
boolean executeRule(T t) {
return true;
}
}
// 具体规则-例子1
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;
}
}
// 具体规则-例子2
public class NationalityRule extends AbstractRule {
@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
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 = "北京";
public static final String MATCH_NATIONALITY_START = "中国";
}The RuleService class builds the execution flow, storing rule lists keyed by AND (1) or OR (0), and provides and() , or() , and execute() methods that iterate through the rules applying short‑circuit logic.
public class RuleService {
private Map
> 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
ruleList) { hashMap.put(AND, ruleList); return this; }
public RuleService or(List
ruleList) { hashMap.put(OR, ruleList); return this; }
public boolean execute(RuleDto dto) {
for (Map.Entry
> item : hashMap.entrySet()) {
List
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
ruleList) {
for (BaseRule rule : ruleList) {
if (!rule.execute(dto)) return false;
}
return true;
}
private boolean or(RuleDto dto, List
ruleList) {
for (BaseRule rule : ruleList) {
if (rule.execute(dto)) return true;
}
return false;
}
}A test class demonstrates chainable construction and execution of the rule engine, combining nationality, name, and address rules with AND logic and age/subject rules with OR logic.
public class RuleServiceTest {
@org.junit.Test
public void execute() {
// define rules
AgeRule ageRule = new AgeRule();
NameRule nameRule = new NameRule();
NationalityRule nationalityRule = new NationalityRule();
AddressRule addressRule = new AddressRule();
SubjectRule subjectRule = new SubjectRule();
// create DTO
RuleDto dto = new RuleDto();
dto.setAge(5);
dto.setName("张三");
dto.setAddress("北京");
dto.setSubject("数学");
// chain and 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);
}
}The author concludes that the engine is simple and modular, allowing independent rule development, but notes the drawback of shared DTO data coupling, recommending pre‑construction of required data to improve design.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.