在面向谓词/规则编程https://blog.csdn.net/GengMingHui_5/article/details/126017267一文中,我们用OO的思想考察了流程/活动图中的菱形块,我们把它视为条件判断——谓词对象。带箭头或不带箭头的线条表示控制流的流向,表示顺序,表示转移(Transfer)这个好理解;那么流程图中的圆角矩形块的本质又是啥呢?也就是说,转移的究竟是什么?实际就是状态对象。流程图是表示程序控制流走向的这种抽象的图形表示法。即程序的本质,从状态流转的角度看就是自动机,FSM(有限状态机)。事件作为动力因,触发状态的转移;状态的转移过程,往往又伴随着被动/回调逻辑的执行。事件源既可以是FSM外部的干涉,也可能是内部自旋锁的打开使得条件判定为真。于是控制流转移到一下个矩形块(状态对象)。这就是流程执行的全部奥秘。下面,先铺垫几个问题:
我们说状态时,我们在说啥?状态,隐含的意义就是:
尽管状态,即变量值会改变,但结构,就是这个对象有什么域,这个通常是不变的。当然,动态型语言可以在运行时添加删除属性,但你知道,我们说结构时仍然指代它有什么。
我们常讲“数据结构和算法”,算法是谁的算法,算的什么?算法的范畴谁决定的,为什么会有P和NP问题?
自动机为什么可能,这也干系到我们能够编程去抽象解决问题何以可能的问题。我认为,最重要的一个前提就是:状态毕竟有限。这也是为什么叫FSM(Finite-state Machine)有限状态机的原因。这也是我们编码所用的语言中为什么会引入异常机制的原因,对于不可预知的情况,我们可以统一抽象归为异常状态,就像填表时,备选项覆盖不全,通常会给一个“其他”一样。这个“其他”,是以上可选项的补集,然而它也是无限的,只是当异常发生我们把无限得可能状态摘要得对应为一种处理逻辑了。当然,无限状态机只是理论上的,现实中不可能出现,这里深究起来涉及图灵机/图灵完备/可计算性/停机问题/哥德尔不完备定理等一系列的数学证明。
有了以上铺垫,我们继续考察文章伊始所谈及流程可以被视为状态机,流程图中矩形块可以被视为状态对象,以及“事件触发状态的转移;状态的转移过程,往往又伴随着被动/回调逻辑的执行”这一机制。我们编程活动,抛开具体业务语义不说,其实就是在编写一些控制流,我们暂且叫它调度机制吧。对于事件的发生,从程序的角度而言,只能通过提供的接口被调用或监听一些系统内部状态值的改变来感知事件的发生。因此,整个流程图,描述的其实就是在事件中各种谓词对象的真值表情况以及控制流流转,即状态的流转这一过程。我们就是在用编程语言描述这一整体的调度机制。至此,以上啰嗦这一堆,对于我们具体编程的指导意义在哪里呢?重要的结论就是:
关于规则引擎得使用有很多库/框架。这里我们使用一个轻量级得easy-rule(本篇代码我用的easy-rules-core-4.1.0.jar),简单易用。来看一个简单示例,将1-50中能同时被5和7整除得数字找出。
package designpattens.ruleengine;
import org.jeasy.rules.annotation.*;
/**
* 能被5和7同时整除的数字
*/
@Rule(name = "FiveSevenRule",description = "同时被5和7整除") // 也可以编程方式实现Rule接口,或使用RuleBuilder建造者来构建规则
public class FiveSevenRule{
// 标注这是一个条件(谓词对象)
@Condition
public boolean isFiveSevenRule (@Fact("number") Integer number) {
return number % 5 == 0 && number % 7 == 0;
}
// 满足条件时执行这个动作
@Action(order = 1)
public void action1(@Fact("number") Integer number) {
System.out.println("找到能被5和7同时整除的数字=====>"+number);
}
// 满足条件时执行动作二
@Action(order = 2)
public void action2(@Fact("number") Integer number) {
System.out.print("动作二");
}
// 在规则链中的优先级,值越低优先级越高
@Priority
public int getPriority() {
return 1;
}
}
package designpattens.ruleengine;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngineListener;
/**
* 规则引擎监听器
*/
public class MyRuleEngineListener implements RulesEngineListener {
/**
* 在执行规则集之前触发
* @param rules 要触发的规则集
* @param facts 触发规则前的事实
*/
public void beforeEvaluate(Rules rules, Facts facts) {
System.out.println("规则引擎beforeEvaluate=================================");
}
/**
* 在执行规则集之后触发
* @param rules 要触发的规则集
* @param facts 触发规则前的事实
*/
public void afterExecute(Rules rules, Facts facts) {
System.out.println("规则引擎afterEvaluate=================================");
}
}
package designpattens.ruleengine;
import org.jeasy.rules.annotation.*;
import java.util.function.Predicate;
/**
* 互斥规则
* 即不能被5、7同时整除
*/
@Rule(name = "NonFiveSevenRule",description = "非同时被5和7整除")
public class NonFiveSevenRule {
@Condition
public boolean nonFiveSevenRule (@Fact("number") Integer number) {
Predicate<Integer> predicate = num-> num % 5 != 0 || num % 7 !=0;
return predicate.test(number);
}
@Action
public void printInput(@Fact("number") Integer number) {
System.out.println( "不能同时被5和7整除数字=====>" + number);
}
@Priority
public int getPriority() {
return 2;
}
}
package designpattens.ruleengine;
import org.jeasy.rules.annotation.*;
/**
* 普通数字规则
* @author gmh
* 2022/5/5 10:30
*/
@Rule(name = "NormalNumRule",description = "普通数字处理规则")
public class NormalNumRule {
@Condition
public boolean nonFiveSevenRule (@Fact("number") Integer number) {
return true;
}
@Action
public void printInput(@Fact("number") Integer number) {
System.out.println( "普通数字=====>" + number);
}
@Priority
public int getPriority() {
return 3;
}
}
package designpattens.ruleengine;
import org.jeasy.rules.api.*;
import org.jeasy.rules.core.DefaultRulesEngine;
/**
* 创建规则引擎
*/
public class RuleEngine {
/*
// 面向过程的写法
public void fizzbuzz() {
for(int i = 1; i <= 50; i++) {
if (((i % 5) == 0) && ((i % 7) == 0)){
System.out.print("target number:"+i);
}else {
System.out.print("normal number:"+i);
}
}
}
*/
public static void main(String[] args) {
// 创建规则引擎,设置规则参数策略
/*
rulePriorityThreshold:当优先级超过指定的阈值时,跳过余下的规则。
skipOnFirstAppliedRule:当一个规则成功应用时,跳过余下的规则。
skipOnFirstFailedRule:当一个规则应用失败时,跳过余下的规则。
skipOnFirstNonTriggeredRule:当一个规则未触发时,跳过余下的规则
*/
RulesEngineParameters rulesEngineParameters = new RulesEngineParameters().skipOnFirstAppliedRule(false);
DefaultRulesEngine rulesEngine = new DefaultRulesEngine(rulesEngineParameters);
// 注册规则监听器和规则引擎监听器
rulesEngine.registerRulesEngineListener(new MyRuleEngineListener());
RuleListener myRuleListener = new RuleListener() {
@Override
public void onSuccess(Rule rule, Facts facts) {
System.out.println("规则执行成功回调!!!");
}
};
rulesEngine.registerRuleListener(myRuleListener);
// 创建规则链
Rules rules = new Rules();
rules.register(new FiveSevenRule());
rules.register(new NonFiveSevenRule());
rules.register(new NormalNumRule());
// 根据既定事实触发规则
Facts facts = new Facts();
for (int i = 1; i <= 50; i++) {
facts.put("number", i);
// 发射规则和事实
rulesEngine.fire(rules, facts);
}
}
}
你可能会不屑,明明写个for循环就解决得小问题,干嘛绕来绕去?其实麻雀虽小,五脏俱全。这里重点要展示的是这种抽象,玩味得是如何将我们思考问题时的一些规则进行抽象。如果你拿去运行了,你就知道,以小见大,对于复杂问题,我们可以利用规则抽象得方式,动态组装规则引擎,从而完成动态逻辑!而不再是依靠写死得if…else…代码来作控制流,每次改动逻辑都要去修改代码。
这里我们以一个最简单得“登录验证到主页”得一个小流程来展示。如果录入不对,就继续录入,来实现流程图中得回向跳转和循环。
package designpattens.state.ruleengine.stateandruleengine;
/**
* 认证流程状态
* @author gmh
* 2022/5/12 9:26
*/
public interface IAuthState {
String getName();
void enterState();
IAuthState checkTransfer();
}
package designpattens.state.ruleengine.stateandruleengine;
/**
* 表单录入状态
* @author gmh
* 2022/5/12 9:30
*/
public class FormInputState implements IAuthState{
@Override
public String getName() {
return "forminput";
}
@Override
public void enterState() {
System.out.println("进入登录页面-表单录入状态");
FormInputRule formInputRule = new FormInputRule();
if (formInputRule.evaluate()) {
formInputRule.execute();
}
}
@Override
public IAuthState checkTransfer() {
if(InfoContainer.contains("toAuth")){
return new AuthenticationState();
}
return null;
}
}
package designpattens.state.ruleengine.stateandruleengine;
/**
* 认证状态
* @author gmh
* 2022/5/12 9:32
*/ public class AuthenticationState implements IAuthState{
@Override
public String getName() {
return "authentication";
}
@Override
public void enterState() {
System.out.println("进入认证状态");
AuthenticationRule authenticationRule = new AuthenticationRule();
if (authenticationRule.evaluate()) {
authenticationRule.execute();
}
}
@Override
public IAuthState checkTransfer() {
String authResult = InfoContainer.getInfo("authResult");
if (null!=authResult && authResult.equalsIgnoreCase("true")) {
return new HomeState();
}else {
return new FormInputState();
}
}
}
package designpattens.state.ruleengine.stateandruleengine;
/**
* 主页数据加载
* @author gmh
* 2022/5/12 9:33
*/
public class HomeState implements IAuthState{
@Override
public String getName() {
return "home";
}
@Override
public void enterState() {
System.out.println("进入主页状态");
HomeLoadRule homeLoadRule = new HomeLoadRule();
if (homeLoadRule.evaluate()) {
homeLoadRule.execute();
}
}
@Override
public IAuthState checkTransfer() {
return new FinishState();
}
}
package designpattens.state.ruleengine.stateandruleengine;
/**
* 终止态
* @author gmh
* 2022/5/12 11:04
*/
public class FinishState implements IAuthState{
@Override
public String getName() {
return "finish";
}
@Override
public void enterState() {
System.out.println("流程结束<<");
}
@Override
public IAuthState checkTransfer() {
return null;
}
}
package designpattens.state.ruleengine.stateandruleengine;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Priority;
import org.jeasy.rules.annotation.Rule;
import java.util.Random;
/**
* 表单录入规则
* @author gmh
* 2022/5/12 9:34
*/
@Rule(name = "forminput",description = "表单录入规则")
public class FormInputRule {
@Condition
public boolean evaluate(){
return true;
}
@Action
public void execute(){
// 录入表单
System.out.println("正在输入登录信息...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Random random=new Random();
int i = random.nextInt(10);
if(i%2==1){
InfoContainer.addInfo("toAuth","admin:123456");
}else {
InfoContainer.addInfo("toAuth","admin:111111");
}
System.out.println("输入信息:"+InfoContainer.getInfo("toAuth"));
}
@Priority
public int getPriority(){
return 0;
}
}
package designpattens.state.ruleengine.stateandruleengine;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Priority;
import org.jeasy.rules.annotation.Rule;
/**
* 认证规则
* @author gmh
* 2022/5/12 9:56
*/
@Rule(name = "Authentication",description = "认证提交")
public class AuthenticationRule {
@Condition
public boolean evaluate(){
return InfoContainer.contains("toAuth");
}
@Action
public void execute(){
final String toAuth = InfoContainer.getInfo("toAuth");
final String[] split = toAuth.split(":");
if(split[0].equals("admin")&&split[1].equals("111111")){
InfoContainer.addInfo("authResult","true");
System.out.println("认证成功");
}else {
InfoContainer.addInfo("authResult","failed");
System.out.println("认证失败");
}
}
@Priority
public int getPriority(){
return 1;
}
}
package designpattens.state.ruleengine.stateandruleengine;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Rule;
/**
* 主页数据加载规则
* @author gmh
* 2022/5/12 10:05
*/
@Rule(name = "home",description = "进入主页加载数据")
public class HomeLoadRule {
@Condition
public boolean evaluate(){
String authResult = InfoContainer.remove("authResult");
return null!= authResult && authResult.equalsIgnoreCase("true");
}
@Action
public void execute(){
InfoContainer.addInfo("home","currUser:admin");
System.out.println("主页数据:"+InfoContainer.getInfo("home"));
}
}
package designpattens.state.ruleengine.stateandruleengine;
import java.util.HashMap;
import java.util.Map;
/**
* 信息容器
* @author gmh
* 2022/5/12 9:36
*/
public class InfoContainer {
private static final Map<String,String> infoCtn = new HashMap<>();
public static String getInfo(String key){
return infoCtn.get(key);
}
public static void addInfo(String key,String value){
infoCtn.put(key,value);
}
public static boolean contains(String key){
return infoCtn.containsKey(key);
}
public static String remove(String key){
return infoCtn.remove(key);
}
}
package designpattens.state.ruleengine.stateandruleengine;
/**
* 使用状态机+规则引擎进行业务调度
* @author gmh
* 2022/5/12 9:28
*/
public class AuthBiz implements Runnable{
private IAuthState currState;
public AuthBiz(IAuthState currState) {
this.currState = currState;
}
public AuthBiz setCurrState(IAuthState currState) {
this.currState = currState;
return this;
}
@Override
public void run() {
currState.enterState();
while (!currState.getName().equalsIgnoreCase("home")) {
IAuthState nState = currState.checkTransfer();
if (null != nState && !nState.getName().equalsIgnoreCase(currState.getName())) {
this.setCurrState(nState);
currState.enterState();
}
}
}
public static void main(String[] args) {
IAuthState initState = new FormInputState();
Thread thread = new Thread(new AuthBiz(initState));
thread.start();
}
}
模拟的可能有些粗糙,请忽略细节看思想。值得注意的是,这里我把状态划分为表单录入,认证,进入主页(认证成功)几种。其实使用状态模式编程不在于你怎样划分状态,重点在于状态之间的流转能闭环自洽,来完成你的业务。再就是,状态对象,既然叫状态对象,每个状态对象都是应该有自己的状态的。而这里我用了享元模式,将各个状态对象应该持有维护的状态数据和并在一起,放入叫做InfoContainer的信息容器对象作为一个大而全的状态对象了;而这也体现了一个深刻的含义,在一项领域业务活动中,所有的状态数据全集,就描述了这个领域的全部。我们五花八门的写法,不过是分还是合的问题。那么推而广之,如果把围绕各个状态的操作也集中到一个对象身上,始得每个对象需要执行各自业务时都去与它通讯,由它统一调度派发,那是什么模式?——中介者模式。