首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

用 BeanShell 實現靈活強大的公式管理系統

     編輯:Java 相關知識

痛點:頻繁變更的業務公式怎麼辦?

在 SCM、CRM、ERP 等中大型應用中,用戶常需根據實際情況靈活調整參數,使固定公式計算結果符合當前狀況——如不同時期商品折扣率、員工獎金比例隨業績變動。若每次變更都改程序,面對頻繁大量變更將不堪重負。本文介紹一種基於 BeanShell 腳本結合 JDOM 實現的簡單易擴展公式管理系統。

技術背景速覽

  • BeanShell:小型、簡單、符合 Java 風格的 Java 解釋器,腳本語言基本與 Java 兼容。非介紹語法,而是展示其強大腳本功能簡化編程工作。
  • JDOM:讓 Java 操作 XML 更輕鬆。此處用 XML 存儲用戶自定義公式庫,簡單易管理。

閱讀前建議對兩者有基礎了解。

系統目標與核心用例

主要目標:

  1. 用戶自定義公式(增刪改公式及參數)
  2. 提供接口供用戶/其他系統調用公式庫計算求值

兩大核心用例:

  • 自定義公式:管理員預定義參數(名、類型),用運算符組合成公式,保存至公式庫
  • 計算表達式:用戶給參數賦值,指定公式 ID 進行計算,系統提供外部接口

公式管理系統用例圖

核心類設計:為 BeanShell 鋪路

核心類圖

  • FormulaParser(公式管理器):轉換用戶自定義公式為系統格式並存儲,提供外部計算接口
  • Calculator(計算器):包含所有運算類型(加減乘除、大小於等於等),可擴展
  • Formula(公式對象):代表一個公式,含參數列表與計算腳本

設計人員中至少應有一位語言專家,才能設計得更靈活簡單,為編碼鋪墊。

何時應用 BeanShell?關鍵判斷標準

當系統某部分變化極大,需動態調用方法和參數時,可嘗試引入 BeanShell。若問題變簡單且靈活性大增,值得嘗試;若未簡化反更複雜,需反思方法或諮詢專家。不追求新技術,追求提高生產效率的新方法。

BeanShell 在系統中的核心應用:外部接口計算

外部系統調用接口傳入:公式 ID + Hashtable「參數名-參數值」列表。FormulaParser 根據 ID 載入公式,賦值參數,按腳本計算。無 BeanShell 時需字符串解析轉換、賦值、求值——極複雜,尤其運算符優先級多時。作者曾見 1000+ 行代碼的簡單解析器,大部分在做字符串解析。

公式庫 XML 示例:

<formulas>
  <formula id="1001" name="F_DISCOUNT">
    <parameters>
      <parameter type="double" name="price"/>
      <parameter type="double" name="discount"/>
    </parameters>
    <script>result= Calculator.mutiply(price, discount)</script>
  </formula>
  <formula id="1002" name="F_BONUS">
    <parameters>
      <parameter type="double" name="sale"/>
      <parameter type="double" name="score"/>
    </parameters>
    <script>result= Calculator.add(Calculator.mutiply(sale, 0.1), 
      Calculator.mutiply(score, 10000))</script>
  </formula>
</formulas>

每公式唯一 ID + 易記名字,含 parameters(參數列表)和 script(BeanShell 腳本)。優勢:無需解析表達式字符串,用 BeanShell 解釋器直接求值。

核心計算代碼(僅一行求值):

public double caculateByFormula(Formula formula, Hashtable parameters) {
    double result = 0.0;
    try {
        Interpreter i = new Interpreter();
        i.eval("import parse.*;");
        Vector para = formula.getParameters();
        Iterator it = para.iterator();
        while (it.hasNext()) {
            String[] dec = (String[])it.next();
            String declare = dec[1] + " " + dec[0];
            i.eval(declare);
            String value = ((Double)parameters.get(dec[0])).toString();
            if (value != null) {
                String assign_value = dec[0] + "=" + value;
                i.eval(assign_value);
            } else {
                System.out.println("參數名不符或不存在"); System.exit(1);
            }
        }
        // 關鍵一行:BeanShell 完成求值
        i.eval(formula.getScript());
        Double rst = (Double)i.get("result");
        result = rst.doubleValue();
    } catch (Exception e) {
        System.out.println("caculateByFormula(): " + e.getMessage());
    }
    return result;
}

JDOM 載入公式:從 XML 組裝 Formula 對象

public Formula loadFormula(String formulaID) {
    Vector paras = new Vector();
    try {
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(prefix + "Formulas.xml");
        Element root = doc.getRootElement();
        List formulas = root.getChildren("formula");
        Iterator it = formulas.iterator();
        Element formula = null;
        while (it.hasNext()) {
            formula = (Element)it.next();
            if (formula.getAttributeValue("id").equals(formulaID)) break;
        }
        List parameters = formula.getChild("parameters").getChildren();
        Iterator itp = parameters.iterator();
        while (itp.hasNext()) {
            String[] s_para = new String[2];
            Element e_para = (Element)itp.next();
            s_para[0] = e_para.getAttributeValue("name");
            s_para[1] = e_para.getAttributeValue("type");
            paras.add(s_para);
        }
        Element script = formula.getChild("script");
        String s_script = script.getTextTrim();
        return new Formula(s_script, paras);
    } catch (Exception e) {
        System.out.println("loadFormula(): " + e.getMessage());
    }
    return null;
}

系統完整圖解:序列圖與狀態圖

調用公式計算外部接口序列圖

FormulaParser 狀態圖

自定義公式界面:建議用戶直接寫 BeanShell 腳本

自定義公式功能非難事,甚至可直接編輯 XML。但開發友好界面時,將用戶輸入轉為參數列表和計算腳本不可避免陷入字符串解析。建議讓用戶直接寫 BeanShell 計算腳本——看公式庫示例腳本即可理解。此部分非本文重點,讀者自行選擇合適方式實現。

測試驗證:預期結果完全一致

FormulaParser fp = new FormulaParser();
Hashtable paras = new Hashtable();
paras.put("price", new Double(100.0));
paras.put("discount", new Double(0.9));
System.out.println("計算結果:" + fp.caculateByFormula(fp.loadFormula("1001"), paras));
// 預期 90.0

FormulaParser fp1 = new FormulaParser();
Hashtable paras1 = new Hashtable();
paras1.put("sale", new Double(11000.0));
paras1.put("score", new Double(0.8));
System.out.println("計算結果:" + fp1.caculateByFormula(fp1.loadFormula("1002"), paras1));
// 預期 9100.0

輸出:

計算結果:90.0
計算結果:9100.0

開發環境:JDK 1.3.1。

結語

這就是 BeanShell 帶來的奇妙體驗。系統實用,可擴展更多運算法則、集成至現有工作、提供友好界面。增強版已成功應用於電信系統。方法未必最佳,歡迎討論。源碼見參考資料。衷心希望「開源世界裡充滿了思想者」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved