1、基本模型
my-statemachine
使用一个工厂(factory)创建一个状态机的构造器(builder),builder包含5个要素:起始状态(from)、目标状态(to)、触发事件(on)、条件判断(when)、执行动作(perform),再使用builder构建一个状态机实例,传入起始状态、触发事件、相关参数(非必要)启动状态机。
public void testExternalNormal() {
StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
builder.externalTransition()
.from(States.STATE1)
.to(States.STATE2)
.on(Events.EVENT1)
.when(checkCondition())
.perform(doAction());
StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);
States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());
Assert.assertEquals(States.STATE2, target);
}
my-rulemachine
创建Facts,使用Rule构造器(builder)创建,builder包含五个要素:规则名称(name)、规则的描述(description)、规则优先级(priority)、判断条件(when)、执行动作(then),构建完成rule后注册到rules中,创建一个规则引擎实例,传入rules和facts启动规则引擎。
public void testAirCond() {
Facts facts = new Facts();
facts.put("temperature", 30);
Rule airConditioningRule = new RuleBuilder()
.name("air conditioning rule")
.when(itIsHot())
.then(decreaseTemperature())
.build();
Rules rules = new Rules();
rules.register(airConditioningRule);
RulesEngine rulesEngine = new InferenceRulesEngine();
rulesEngine.fire(rules, facts);
}
2、构造器的级联赋值属性
my-statemachine
通过在interface中声明方法串联,builder实现这些接口,所有接口返回builder自身:
点评:由于使用了三种类型的构造器,这样可以严格控制每种构造器所需要赋值的参数类型及顺序。
public interface ExternalTransitionBuilder<S, E, C> {
/**
* 构建transition的初始状态
* @param stateId
* @return
*/
From<S, E, C> from(S stateId);
}
public interface From<S, E, C> {
/**
* 构建transition的目标状态
* @param stateId
* @return
*/
To<S, E, C> to(S stateId);
}
public interface To<S, E, C> {
/**
* 构建transition的触发事件
* @param event
* @return
*/
On<S, E, C> on(E event);
}
public interface On<S, E, C> {
/**
* 构建transition的触发条件
* @param condition
* @return
*/
When<S, E, C> when(Condition<C> condition);
}
public interface When<S, E, C> {
/**
* 构建transition的执行
* @param action
*/
void perform(Action<S, E, C> action);
}
public class TransitionBuilderImpl<S, E, C> implements ExternalTransitionBuilder<S, E, C>, InternalTransitionBuilder<S, E, C>,
From<S, E, C>, On<S, E, C>, To<S, E, C>, When<S, E, C> {
final Map<S, State<S, E, C>> stateMap;
private State<S, E, C> source;
protected State<S, E, C> target;
private Transition<S, E, C> transition;
final TransitionType transitionType;
public TransitionBuilderImpl(Map<S, State<S, E, C>> stateMap, TransitionType transitionType) {
this.stateMap = stateMap;
this.transitionType = transitionType;
}
@Override
public From<S, E, C> from(S stateId) {
source = StateHelper.getState(stateMap, stateId);
return this;
}
@Override
public To<S, E, C> to(S stateId) {
target = StateHelper.getState(stateMap, stateId);
return this;
}
@Override
public To<S, E, C> within(S stateId) {
source = target = StateHelper.getState(stateMap, stateId);
return this;
}
@Override
public On<S, E, C> on(E event) {
transition = source.addTransition(event, target, transitionType);
return this;
}
@Override
public When<S, E, C> when(Condition<C> condition) {
transition.setCondition(condition);
return this;
}
@Override
public void perform(Action<S, E, C> action) {
transition.setAction(action);
}
}
my-rulemachine
直接声明与参数同名的方法,返回builder自身:
点评:由于参数给了默认值,因此一些非必要参数可以选择不设置,代码简洁。
public class RuleBuilder {
private String name = Rule.DEFAULT_NAME;
private String description = Rule.DEFAULT_DESCRIPTION;
private int priority = Rule.DEFAULT_PRIORITY;
private Condition condition = Condition.FALSE;
private final List<Action> actions = new ArrayList<>();
public RuleBuilder name(String name) {
this.name = name;
return this;
}
public RuleBuilder description(String description) {
this.description = description;
return this;
}
public RuleBuilder priority(int priority) {
this.priority = priority;
return this;
}
public RuleBuilder when(Condition condition) {
this.condition = condition;
return this;
}
public RuleBuilder then(Action action) {
this.actions.add(action);
return this;
}
public Rule build() {
return new DefaultRule(name , description, priority, condition, actions);
}
}
3、使用函数式接口将条件判断和执行方法作为入参赋予builder
两者都利用了函数式接口
函数式接口:当接口只有一个抽象方法的时候(可以有其它默认方法),就是函数式接口,可以使用注解(@FunctionalInterface)强制限定接口只能有一个抽象方法。
my-statemachine
可以使用lamda表达式,适合简单的方法,实现起来更简洁。
public interface Condition<C> {
/**
* 用户实现的条件判断
* @param context
* @return
*/
boolean isSatisfied(C context);
}
public interface Action<S, E, C> {
/**
* 用户实现的状态转移成功时的动作
*/
void execute(S from, S to, E event, C context);
}
private Condition<Context> checkCondition() {
return (ctx) -> {
return true;
};
}
private Action<States, Events, Context> doAction() {
return (from, to, event, ctx) -> {
System.out.println(ctx.operator + " is operating " + ctx.entityId + " from:" + from + " to:" + to + " on:" + event);
};
}
my-rulemachine
可以使用实现类,new一个对象执行对象下的方法,适合复杂的方法
@FunctionalInterface
public interface Condition {
boolean evaluate(Facts facts);
/**
* 无论什么入参一律输出false
*/
Condition FALSE = facts -> false;
/**
* 无论什么入参一律输出true
*/
Condition TRUE = facts -> true;
}
@FunctionalInterface
public interface Action {
void execute(Facts facts) throws Exception;
}
public class HighTemperatureCondition implements Condition {
static HighTemperatureCondition itIsHot() {
return new HighTemperatureCondition();
}
@Override
public boolean evaluate(Facts facts) {
Integer temperature = facts.get("temperature");
return temperature > 25;
}
}
public class DecreaseTemperatureAction implements Action {
static DecreaseTemperatureAction decreaseTemperature() {
return new DecreaseTemperatureAction();
}
@Override
public void execute(Facts facts) {
System.out.println("It is hot! cooling air..");
Integer temperature = facts.get("temperature");
facts.put("temperature", temperature - 1);
}
}
4、使用工厂而不是直接new一个builder
my-statemachine
在factory中声明一个ConcurrnetHashMap,用来存储machineId及对应的状态机,可以对状态机进行复用。
特别适合多状态转移的情况使用
public void testExternalInternalNormal() {
StateMachine<States, Events, Context> stateMachine = buildStateMachine("testExternalInternalNormal");
Context context = new Context();
States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, context);
Assert.assertEquals(States.STATE2, target);
target = stateMachine.fireEvent(States.STATE2, Events.INTERNAL_EVENT, context);
Assert.assertEquals(States.STATE2, target);
target = stateMachine.fireEvent(States.STATE2, Events.EVENT2, context);
Assert.assertEquals(States.STATE1, target);
target = stateMachine.fireEvent(States.STATE1, Events.EVENT3, context);
Assert.assertEquals(States.STATE3, target);
}
5、有层次的对象
my-statemachine
状态机拥有多个状态,每个状态又可以有多个转移对象。
(状态机)stateMachine->(状态对象集合)stateMap->(状态转移对象集合)transitions->source、target、event、condition、action、type
6、基于注解的Rule参数配置
my-rulemachine
这是一个例子,定义一个Rule
@Rule
public class FizzRule {
@Condition
public boolean isFizz(@Fact("number") Integer number) {
return number % 5 == 0;
}
@Action
public void printFizz() {
System.out.print("fizz");
}
@Priority
public int getPriority() {
return 1;
}
}
声明一个RuleProxy implements InvocationHandler, 在将rule register到rules的时候,直接利用动态代理,将FizzRule转换成一个Rule Class,并代理Rule Class下的全部接口。
public class RuleProxy implements InvocationHandler {
...
public static Rule asRule(final Object rule) {
Rule result;
if (rule instanceof Rule) {
result = (Rule) rule;
} else {
ruleDefinitionValidator.validateRuleDefinition(rule);
result = (Rule) Proxy.newProxyInstance(
Rule.class.getClassLoader(),
new Class[]{Rule.class, Comparable.class},
new RuleProxy(rule));
}
return result;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
switch (methodName) {
case "getName":
return getRuleName();
case "getDescription":
return getRuleDescription();
case "getPriority":
return getRulePriority();
case "compareTo":
return compareToMethod(args);
case "evaluate":
return evaluateMethod(args);
case "execute":
return executeMethod(args);
case "equals":
return equalsMethod(args);
case "hashCode":
return hashCodeMethod();
case "toString":
return toStringMethod();
default:
return null;
}
}
...
}
通过原始类获取其上的注解,来实现注解参数的解析
static <A extends Annotation> A findAnnotation(final Class<A> targetAnnotation, final Class<?> annotatedType) {
A foundAnnotation = annotatedType.getAnnotation(targetAnnotation);
if (null == foundAnnotation) {
for (Annotation annotation : annotatedType.getAnnotations()) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotatedType.isAnnotationPresent(targetAnnotation)) {
foundAnnotation = annotationType.getAnnotation(targetAnnotation);
break;
}
}
}
return foundAnnotation;
}
代理执行方法时,通过获取原始Class下的所有Method并判断其注解,然后通过invoke调用。
private Method getConditionMethod() {
if (this.conditionMethod == null) {
Method[] methods = getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Condition.class)) {
this.conditionMethod = method;
return this.conditionMethod;
}
}
}
return this.conditionMethod;
}
private List<Object> getActualParameters(Method method, Facts facts) {
List<Object> actualParameters = new ArrayList<>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation[] annotations : parameterAnnotations) {
if (1 == annotations.length) {
String factName = ((Fact) (annotations[0])).value();
Object fact = facts.get(factName);
if (fact == null && !facts.asMap().containsKey(factName)) {
throw new NoSuchFactException(format("No fact named '%s' found in known facts: %n%s", factName, facts), factName);
}
actualParameters.add(fact);
} else {
actualParameters.add(facts); //validated upfront, there may be only one parameter not annotated and which is of type Facts.class
}
}
return actualParameters;
}
private Object evaluateMethod(final Object[] args) throws IllegalAccessException, InvocationTargetException {
Facts facts = (Facts) args[0];
Method conditionMethod = getConditionMethod();
try {
List<Object> actualParameters = getActualParameters(conditionMethod, facts);
return conditionMethod.invoke(target, actualParameters.toArray()); // validated upfront
} catch (NoSuchFactException e) {
log.warn("Rule '{}' has been evaluated to false due to a declared but missing fact '{}' in {}",
getTargetClass().getName(), e.getMissingFact(), facts);
return false;
} catch (IllegalArgumentException e) {
log.warn("Types of injected facts in method '{}' in rule '{}' do not match parameters types",
conditionMethod.getName(), getTargetClass().getName(), e);
return false;
}
}