TinyのRuleEngine
20093 ワード
ディレクトリ[-]ルールエフェクタインタフェースルールエンジンインタフェースルールセットオブジェクトルール抽象クラスMVEL方式のルールとそのエフェクタMvelルール実行器Mvelコンテキストルールエンジン実装クラス例定義ルール作成TestCase総括ルールエンジンはビジネスルールが頻繁に変化するシーンに適しており、我々のビジネスは応用過程においても、多くのビジネスルールを処理することが多い.もちろん、ルールエンジンで支えてほしいです.これは最高です.
よく使われるビジネスルールエンジンを知っておくと、とてもいい感じですが、高すぎます.オープンソースのエンジンを見てみましょう.いいですが、私たち自身のような簡単な需要に比べて、複雑すぎるような気がします.
そこで、自分でやってみて、自分の簡単なビジネスルールが頻繁に変化するビジネスシーンを解決できるかどうか試してみました.うんうん、頭の中で映画を見て、道が通じているような気がします.主に次のようなビジネスニーズがあります.
業務規則執行器は多種をサポートする必要があり、業務人員の自己拡張もサポートすべきである.なぜなら、私が設計した業務規則がどんなに完璧であっても、すべての人の食欲に完璧に適応することはできないからだ.だから、このデフォルトはサポートできるが、拡張可能な業務規則は優先度をサポートしなければならない.つまり、ある業務規則は先に実行しなければならない.ビジネス・ルールの後に実行されるビジネス・ルールでは、排他ルールが許可されています.つまり、排他ルールを実行すると、すぐにビジネス・ルールを終了して繰り返し実行を許可することができます.これにより、ルール・エンジンでのループ処理が容易になり、Springのビジネス・オブジェクトを便利に使用することができます.
ルールエフェクタインタフェース
ビジネス・ルール・エフェクタは拡張をサポートする必要があるため、もちろんインタフェースを設計する必要があります.
全部で2つの方法について、getTypeはルール実行器のタイプを返して、どのタイプのルールを解決するかを決定します.executeメソッドはルールを実行するために使用され、実行結果はブール値であり、このルールが実行されているかどうかを示します.
ルールエンジンインタフェース
次に、設計ルールエンジンのインタフェースを示します.
上のコードのように、とても簡単です.executeはルールセットを実行するために使用され、他の方法はルールセットとルールエフェクタの管理であり、一度見ればはっきりしている.
ルールセットオブジェクト
ルールセットには2つのプロパティがあり、1つのプロパティはルールセットの名前であり、もう1つのプロパティはルールのセットです.ルール・セットの名前は、関連するビジネス・ルールのセットを表すために使用されます.
ルール抽象クラス
上記のビジネスニーズに基づいて、抽象クラスRuleの構造は以下の通りです.
優先度、識別、排他するかどうか、重複を許可するかどうか、説明、タイトル、タイプ、有効性など、基本的な属性はいくつかしかありません.
約束したビジネスルールはどう説明しますか?
異なるルール・エフェクタでは、サポートできるルールも異なるため、ここでのルール抽象クラスには基本的な属性しかありません.ルールをどのように記述するかは、そのサブクラスによって決まります.
MVEL方式のルールとそのアクチュエータ
Mvelルール
上に示すように、このルールのタイプはすべてmvelであり、このルールには2つの属性が含まれている:conditionとaction、conditionは条件を表し、条件実行結果が本当の場合にのみactionの処理を実行する.
Mvelルールアクチュエータ
executeメソッドは、条件式を実行して真を返すとactionの処理を実行し、trueを返します.そうしないとfalseを返します.ほほほ、この論理は簡単すぎる.はい、tinyフレームワークの大きな特徴は、比較的複雑な処理を非常に簡単な論理で実現することです.
Mvelコンテキスト
前述したように、Springで管理されているオブジェクトを式で簡単に呼び出すには、この実装はコンテキストから記述されます.
主な論理は上にあります.つまり、コンテキストに対像がある場合は、コンテキストから取ります.ない場合はSpring容器から取ります.ほほほ、こんなに高い機能は、実現するのもこんなに簡単です.
ルール・エンジン実装クラス
上まで、関連する準備が整い、ルールエンジンの実装クラスも現れるようになった.実はこの種類は貼らないで、文章を読む学生たちはきっと私が隠していると言っていますが、貼ってください.本当に技術の含有量はありません.
メンテナンス・ルールとルール・エフェクタのコードの山は、重要ないくつかの説明を省略します.
ルール・セットを検索し、見つけられたらルール・セットを実行します.そうしないと、何もしません.
}ルールセットを実行する論理は、ルールセットにルールがない場合、ルールセットが実行済みであることを示し、直接戻ります.優先度が最も高いルールを取得しない場合は、まずオブジェクトのルールエフェクタがあるかどうかを確認し、ない場合は例外を投げます.ある場合は実行を開始します.
実行がtrueを返すと、このルールが正常に実行されたことを示す場合、そのルールが排他ルールであるか否かを判断し、もしそうであれば戻る.そうでなければ、繰り返し実行可能なルールかどうかをチェックし、そうであれば実行を続行します.そうでなければ、このルールを削除し、次のルールを実行します.
例
ここでは、個人所得税を計算するルールの例を想定します.
ルールの定義
TestCaseの作成
ここを見たとき、私の唯一の考えは:いつ私は1ヶ月に3万元の税金を払うことができますか.
まとめ
ふふ、Tinyの慣例に従って、コード統計データを伝えます:
これで、単純なルールエンジンが実現され、合計コード行数には注釈462行が含まれません.さまざまな簡単なビジネスロジックが頻繁に変化するビジネスシーンに適応することができます.
登録への関心を歓迎します:http://web.j2ee.top、この例に関連するコードとフレームワークの資料は、フォーラムで共有されます.技術交流群:22897971、一緒に手を出して、フレームワークの奥義を理解しましょう.
よく使われるビジネスルールエンジンを知っておくと、とてもいい感じですが、高すぎます.オープンソースのエンジンを見てみましょう.いいですが、私たち自身のような簡単な需要に比べて、複雑すぎるような気がします.
そこで、自分でやってみて、自分の簡単なビジネスルールが頻繁に変化するビジネスシーンを解決できるかどうか試してみました.うんうん、頭の中で映画を見て、道が通じているような気がします.主に次のようなビジネスニーズがあります.
業務規則執行器は多種をサポートする必要があり、業務人員の自己拡張もサポートすべきである.なぜなら、私が設計した業務規則がどんなに完璧であっても、すべての人の食欲に完璧に適応することはできないからだ.だから、このデフォルトはサポートできるが、拡張可能な業務規則は優先度をサポートしなければならない.つまり、ある業務規則は先に実行しなければならない.ビジネス・ルールの後に実行されるビジネス・ルールでは、排他ルールが許可されています.つまり、排他ルールを実行すると、すぐにビジネス・ルールを終了して繰り返し実行を許可することができます.これにより、ルール・エンジンでのループ処理が容易になり、Springのビジネス・オブジェクトを便利に使用することができます.
ルールエフェクタインタフェース
ビジネス・ルール・エフェクタは拡張をサポートする必要があるため、もちろんインタフェースを設計する必要があります.
/**
* ,
*/
public interface RuleExecutor<T extends Rule> {
/**
*
*
* @return
*/
String getType();
/**
* ,
*
* @param context
* @return
*/
boolean execute(Context context, T rule);
}
全部で2つの方法について、getTypeはルール実行器のタイプを返して、どのタイプのルールを解決するかを決定します.executeメソッドはルールを実行するために使用され、実行結果はブール値であり、このルールが実行されているかどうかを示します.
ルールエンジンインタフェース
次に、設計ルールエンジンのインタフェースを示します.
public interface RuleEngine {
/**
*
*
* @param context
* @param ruleSetName
*/
void execute(Context context, String ruleSetName);
/**
*
*
* @param ruleSet
*/
void addRules(RuleSet ruleSet);
/**
*
*
* @param ruleSet
*/
void removeRules(RuleSet ruleSet);
/**
*
*
* @param ruleExecutors
*/
void addRuleExecutors(List<RuleExecutor> ruleExecutors);
/**
*
*
* @param ruleExecutor
*/
void addRuleExecutor(RuleExecutor ruleExecutor);
/**
*
*
* @param ruleExecutors
*/
void removeRuleExecutors(List<RuleExecutor> ruleExecutors);
/**
*
*
* @param ruleExecutor
*/
void removeRuleExecutor(RuleExecutor ruleExecutor);
/**
*
* @param ruleExecutors
*/
void setRuleExecutors(List<RuleExecutor> ruleExecutors);
}
上のコードのように、とても簡単です.executeはルールセットを実行するために使用され、他の方法はルールセットとルールエフェクタの管理であり、一度見ればはっきりしている.
ルールセットオブジェクト
@XStreamAlias("rule-set")
public class RuleSet {
/**
*
*/
@XStreamAsAttribute
private String name;
@XStreamImplicit
private List<Rule> rules;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Rule> getRules() {
if(rules==null){
rules = new ArrayList<Rule>();
}
return rules;
}
public void setRules(List<Rule> rules) {
this.rules = rules;
}
}
ルールセットには2つのプロパティがあり、1つのプロパティはルールセットの名前であり、もう1つのプロパティはルールのセットです.ルール・セットの名前は、関連するビジネス・ルールのセットを表すために使用されます.
ルール抽象クラス
上記のビジネスニーズに基づいて、抽象クラスRuleの構造は以下の通りです.
優先度、識別、排他するかどうか、重複を許可するかどうか、説明、タイトル、タイプ、有効性など、基本的な属性はいくつかしかありません.
約束したビジネスルールはどう説明しますか?
異なるルール・エフェクタでは、サポートできるルールも異なるため、ここでのルール抽象クラスには基本的な属性しかありません.ルールをどのように記述するかは、そのサブクラスによって決まります.
MVEL方式のルールとそのアクチュエータ
Mvelルール
/**
* MVEL
* @author yancheng11334
*
*/
@XStreamAlias("mvel-rule")
public class MvelRule extends Rule{
//
private String condition;
//
private String action;
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getType(){
return "mvel";
}
public String toString() {
return "MvelRule [condition=" + condition + ", action=" + action
+ ", type=" + getType() + ", id=" + getId() + ", priority="+ getPriority() +", multipleTimes="+isMultipleTimes()+",exclusive="+isExclusive()+"]";
}
/**
* mvel
*/
public boolean isVaild() {
if(StringUtil.isEmpty(getCondition())){
throw new RuntimeException(String.format(" [%s] ", getId()));
}
if(StringUtil.isEmpty(getAction())){
throw new RuntimeException(String.format(" [%s] ", getId()));
}
return true;
}
}
上に示すように、このルールのタイプはすべてmvelであり、このルールには2つの属性が含まれている:conditionとaction、conditionは条件を表し、条件実行結果が本当の場合にのみactionの処理を実行する.
Mvelルールアクチュエータ
public class MvelRuleExecutor implements RuleExecutor<MvelRule>{
private EL el;
public EL getEl() {
return el;
}
public void setEl(EL el) {
this.el = el;
}
public String getType() {
return "mvel";
}
public boolean execute(Context context, MvelRule rule) {
try{
if(executeCondition(rule.getCondition(),context)){
executeAction(rule.getAction(),context);
return true;
}else{
return false;
}
}catch(RuntimeException e){
throw e;
}catch(Exception e){
throw new RuntimeException("Mvel :",e);
}
}
/**
*
* @param condition
* @param context
* @return
*/
protected boolean executeCondition(String condition,Context context){
try{
MvelContext mvelContext=null;
if(context instanceof MvelContext){
mvelContext=(MvelContext) context;
}else{
mvelContext=new MvelContext(context);
}
return (Boolean)el.execute(condition, mvelContext);
}catch(Exception e){
throw new RuntimeException(String.format(" [%s] :", condition),e);
}
}
/**
*
* @param action
* @param context
*/
protected void executeAction(String action,Context context) {
try {
MvelContext mvelContext = null;
if (context instanceof MvelContext) {
mvelContext = (MvelContext) context;
} else {
mvelContext = new MvelContext(context);
}
el.execute(action, mvelContext);
} catch (Exception e) {
throw new RuntimeException(String.format(" [%s] :", action), e);
}
}
}
executeメソッドは、条件式を実行して真を返すとactionの処理を実行し、trueを返します.そうしないとfalseを返します.ほほほ、この論理は簡単すぎる.はい、tinyフレームワークの大きな特徴は、比較的複雑な処理を非常に簡単な論理で実現することです.
Mvelコンテキスト
前述したように、Springで管理されているオブジェクトを式で簡単に呼び出すには、この実装はコンテキストから記述されます.
public <T> T get(String name) {
if(context.exist(name)){
return (T)context.get(name);
}else{
// , (scope )
T t = (T)beanContainer.getBean(name);
context.put(name, t);
return t;
}
}
主な論理は上にあります.つまり、コンテキストに対像がある場合は、コンテキストから取ります.ない場合はSpring容器から取ります.ほほほ、こんなに高い機能は、実現するのもこんなに簡単です.
ルール・エンジン実装クラス
上まで、関連する準備が整い、ルールエンジンの実装クラスも現れるようになった.実はこの種類は貼らないで、文章を読む学生たちはきっと私が隠していると言っていますが、貼ってください.本当に技術の含有量はありません.
public class RuleEngineDefault implements RuleEngine {
private Map<String, List<Rule>> ruleSetMap = new ConcurrentHashMap<String, List<Rule>>();
private List<RuleExecutor> ruleExecutors = null;
private Map<String, RuleExecutor> ruleExecutorMap = new ConcurrentHashMap<String, RuleExecutor>();
protected static Logger logger = LoggerFactory
.getLogger(RuleEngineDefault.class);
public void execute(Context context, String ruleSetName) {
List<Rule> ruleSet = ruleSetMap.get(ruleSetName);
if (ruleSet != null) {
Vector<Rule> newSet = new Vector<Rule>(ruleSet);
processRuleSet(context, newSet);
}
}
private void processRuleSet(Context context, Vector<Rule> newSet) {
// ,
if (newSet.size() == 0) {
return;
}
Rule rule = newSet.get(0);
RuleExecutor ruleExecutor = ruleExecutorMap.get(rule.getType());
if (ruleExecutor != null) {
boolean executed = ruleExecutor.execute(context, rule);
if (executed) {
//
if (rule.isExclusive()) {
// , ,
return;
} else if (!rule.isMultipleTimes()) {
// ,
newSet.remove(0);
}
} else {
// ,
newSet.remove(0);
}
} else {
throw new RuntimeException(" " + rule.getType() + " ");
}
processRuleSet(context, newSet);
}
public void addRules(RuleSet ruleSet) {
List<Rule> rules = ruleSetMap.get(ruleSet.getName());
if (rules == null) {
rules = new Vector<Rule>();
ruleSetMap.put(ruleSet.getName(), rules);
}
//
for(Rule rule:ruleSet.getRules()){
if(rule.isVaild()){
rules.add(rule);
}else{
logger.logMessage(LogLevel.ERROR, String.format(" [%s] .", rule.getId()));
}
rule.isVaild();
}
Collections.sort(rules);
}
public void removeRules(RuleSet ruleSet) {
List<Rule> rules = ruleSetMap.get(ruleSet.getName());
if (rules != null) {
rules.removeAll(ruleSet.getRules());
}
}
public void setRuleExecutors(List<RuleExecutor> ruleExecutors) {
this.ruleExecutors = ruleExecutors;
for (RuleExecutor ruleExecutor : ruleExecutors) {
ruleExecutorMap.put(ruleExecutor.getType(), ruleExecutor);
}
}
public void addRuleExecutor(RuleExecutor ruleExecutor) {
if (ruleExecutors == null) {
ruleExecutors = new ArrayList<RuleExecutor>();
}
ruleExecutors.add(ruleExecutor);
ruleExecutorMap.put(ruleExecutor.getType(), ruleExecutor);
}
public void addRuleExecutors(List<RuleExecutor> ruleExecutors) {
if(ruleExecutors!=null){
for(RuleExecutor ruleExecutor:ruleExecutors){
addRuleExecutor(ruleExecutor);
}
}
}
public void removeRuleExecutors(List<RuleExecutor> ruleExecutors) {
if(ruleExecutors!=null){
for(RuleExecutor ruleExecutor:ruleExecutors){
removeRuleExecutor(ruleExecutor);
}
}
}
public void removeRuleExecutor(RuleExecutor ruleExecutor) {
if (ruleExecutors == null) {
ruleExecutors = new ArrayList<RuleExecutor>();
}
ruleExecutors.remove(ruleExecutor);
ruleExecutorMap.remove(ruleExecutor.getType());
}
}
メンテナンス・ルールとルール・エフェクタのコードの山は、重要ないくつかの説明を省略します.
public void execute(Context context, String ruleSetName) {
List<Rule> ruleSet = ruleSetMap.get(ruleSetName);
if (ruleSet != null) {
Vector<Rule> newSet = new Vector<Rule>(ruleSet);
processRuleSet(context, newSet);
}
}
ルール・セットを検索し、見つけられたらルール・セットを実行します.そうしないと、何もしません.
private void processRuleSet(Context context, Vector<Rule> newSet) {
// ,
if (newSet.size() == 0) {
return;
}
Rule rule = newSet.get(0);
RuleExecutor ruleExecutor = ruleExecutorMap.get(rule.getType());
if (ruleExecutor != null) {
boolean executed = ruleExecutor.execute(context, rule);
if (executed) {
//
if (rule.isExclusive()) {
// , ,
return;
} else if (!rule.isMultipleTimes()) {
// ,
newSet.remove(0);
}
} else {
// ,
newSet.remove(0);
}
} else {
throw new RuntimeException(" " + rule.getType() + " ");
}
processRuleSet(context, newSet);
}
}ルールセットを実行する論理は、ルールセットにルールがない場合、ルールセットが実行済みであることを示し、直接戻ります.優先度が最も高いルールを取得しない場合は、まずオブジェクトのルールエフェクタがあるかどうかを確認し、ない場合は例外を投げます.ある場合は実行を開始します.
実行がtrueを返すと、このルールが正常に実行されたことを示す場合、そのルールが排他ルールであるか否かを判断し、もしそうであれば戻る.そうでなければ、繰り返し実行可能なルールかどうかをチェックし、そうであれば実行を続行します.そうでなければ、このルールを削除し、次のルールを実行します.
例
ここでは、個人所得税を計算するルールの例を想定します.
ルールの定義
<rule-set name="feerule" >
<!-- ( ) -->
<!-- , , 0; , ; -->
<mvel-rule id="step1" multipleTimes="false" exclusive="true">
<condition><![CDATA[salary<=3500]]></condition>
<action><![CDATA[fee=0]]></action>
</mvel-rule>
<mvel-rule id="step2" multipleTimes="false" exclusive="true">
<condition><![CDATA[salary>3500 && salary<=5000]]></condition>
<action><![CDATA[fee=(salary-3500)*0.03]]></action>
</mvel-rule>
<mvel-rule id="step3" multipleTimes="false" exclusive="true">
<condition><![CDATA[salary>5000 && salary<=8000]]></condition>
<action><![CDATA[fee=(salary-3500)*0.1-105]]></action>
</mvel-rule>
<mvel-rule id="step4" multipleTimes="false" exclusive="true">
<condition><![CDATA[salary>8000 && salary<=12500]]></condition>
<action><![CDATA[fee=(salary-3500)*0.2-555]]></action>
</mvel-rule>
<mvel-rule id="step5" multipleTimes="false" exclusive="true">
<condition><![CDATA[salary>12500 && salary<=38500]]></condition>
<action><![CDATA[fee=(salary-3500)*0.25-1005]]></action>
</mvel-rule>
<mvel-rule id="step6" multipleTimes="false" exclusive="true">
<condition><![CDATA[salary>38500 && salary<=58500]]></condition>
<action><![CDATA[fee=(salary-3500)*0.3-2755]]></action>
</mvel-rule>
<mvel-rule id="step7" multipleTimes="false" exclusive="true">
<condition><![CDATA[salary>58500 && salary<=83500]]></condition>
<action><![CDATA[fee=(salary-3500)*0.35-5505]]></action>
</mvel-rule>
<mvel-rule id="step8" multipleTimes="false" exclusive="true">
<condition><![CDATA[salary>83500]]></condition>
<action><![CDATA[fee=(salary-3500)*0.45-13505]]></action>
</mvel-rule>
</rule-set>
TestCaseの作成
public void testFeeRule(){
Context context = new MvelContext();
context.put("fee", 0.0);
context.put("salary", 1000);
ruleEngine.execute(context, "feerule");
assertEquals(0, context.get("fee"));
context.put("salary", 4000);
ruleEngine.execute(context, "feerule");
assertEquals(15.0, context.get("fee"));
context.put("salary", 7000);
ruleEngine.execute(context, "feerule");
assertEquals(245.0, context.get("fee"));
context.put("salary", 21000);
ruleEngine.execute(context, "feerule");
assertEquals(3370.0, context.get("fee"));
context.put("salary", 40005);
ruleEngine.execute(context, "feerule");
assertEquals(8196.50, context.get("fee"));
context.put("salary", 70005);
ruleEngine.execute(context, "feerule");
assertEquals(17771.75, context.get("fee"));
context.put("salary", 100000);
ruleEngine.execute(context, "feerule");
assertEquals(29920.00, context.get("fee"));
}
ここを見たとき、私の唯一の考えは:いつ私は1ヶ月に3万元の税金を払うことができますか.
まとめ
ふふ、Tinyの慣例に従って、コード統計データを伝えます:
これで、単純なルールエンジンが実現され、合計コード行数には注釈462行が含まれません.さまざまな簡単なビジネスロジックが頻繁に変化するビジネスシーンに適応することができます.
登録への関心を歓迎します:http://web.j2ee.top、この例に関連するコードとフレームワークの資料は、フォーラムで共有されます.技術交流群:22897971、一緒に手を出して、フレームワークの奥義を理解しましょう.