TinyのRuleEngine

20093 ワード

ディレクトリ[-]ルールエフェクタインタフェースルールエンジンインタフェースルールセットオブジェクトルール抽象クラスMVEL方式のルールとそのエフェクタMvelルール実行器Mvelコンテキストルールエンジン実装クラス例定義ルール作成TestCase総括ルールエンジンはビジネスルールが頻繁に変化するシーンに適しており、我々のビジネスは応用過程においても、多くのビジネスルールを処理することが多い.もちろん、ルールエンジンで支えてほしいです.これは最高です.
よく使われるビジネスルールエンジンを知っておくと、とてもいい感じですが、高すぎます.オープンソースのエンジンを見てみましょう.いいですが、私たち自身のような簡単な需要に比べて、複雑すぎるような気がします.
そこで、自分でやってみて、自分の簡単なビジネスルールが頻繁に変化するビジネスシーンを解決できるかどうか試してみました.うんうん、頭の中で映画を見て、道が通じているような気がします.主に次のようなビジネスニーズがあります.
業務規則執行器は多種をサポートする必要があり、業務人員の自己拡張もサポートすべきである.なぜなら、私が設計した業務規則がどんなに完璧であっても、すべての人の食欲に完璧に適応することはできないからだ.だから、このデフォルトはサポートできるが、拡張可能な業務規則は優先度をサポートしなければならない.つまり、ある業務規則は先に実行しなければならない.ビジネス・ルールの後に実行されるビジネス・ルールでは、排他ルールが許可されています.つまり、排他ルールを実行すると、すぐにビジネス・ルールを終了して繰り返し実行を許可することができます.これにより、ルール・エンジンでのループ処理が容易になり、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、一緒に手を出して、フレームワークの奥義を理解しましょう.