Динамически загружать математические формулы и оценивать значения с помощью Script Engine - PullRequest
2 голосов
/ 27 октября 2019

Мне нужно реализовать функцию для программы, которая вычисляет метрики на основе предварительно определенных мер с требованием, чтобы добавление новой метрики (и / или меры) требовало минимальных изменений уровня кода. Вот как я это сделал:

class Measure { // clas to hold the measures
    Integer id;
    String name;
    Long value;

    // necessary getters and setters
}

Эти меры извлекаются из БД MySQL.

// the measures stored in an ArrayList of objects of the above Measure class
[
    {
        "id": 1,
        "name": "Displacement",
        "value": 200
    },
    {
        "id": 2,
        "name":"Time",
        "value": 120
    },
    {
        "id":3,
        "name":"Mass",
        "value": 233
    },
    {
        "id":4,
        "name": "Acceleration",
        "value": 9.81
    },
    {
        "id": 5,
        "name":"Speed of Light",
        "value": 300000000
    }
]

Мне нужно получить такие метрики, как: Velocity (Displacement/Time), Force (Mass * Acceleration) и Energy (Mass * Speed of Light^2)

Я реализовал следующий JSON, в котором я определил вышеупомянутые формулы:

[
    {
        "title": "Velocity",
        "unit": "m/s",
        "formula": "( displacement / time )"
    },
    {
        "title": "Force",
        "unit": "N",
        "formula": "( mass * acceleration )"
    },
    {
        "title": "Energy",
        "unit": "J",
        "formula": "( mass * speed_of_light * speed_of_light )"
    }
]

Затем я вычисляю метрики следующим образом:

class Evaluator {
    ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
    public Evaluator(List<Measure> measures) {
        measures.forEach(measure -> {
            String fieldName = measure.getName().replace(" ", "_").toLowerCase(); // convert Speed of Light -> speed_of_light etc
            engine.put(fieldName, Double.parseDouble(measure.getValue()));
        })
    }

    public void formula(String formula) throws Exception {
        engine.eval("function calculateMetric() { return " + formula +" }");
    }

    public Object evaluate() throws ScriptException {
        return engine.eval("calculateMetric()");
    }
}

Приведенный выше класс используется для загрузки каждой формулы в Script Engine, а затем для вычисления значения метрики на основе предоставленной формулы.

// load JSON file into JsonArray
JsonArray formulae = parseIntoJsonArray(FileUtils.getContent("formulae.json"));
Evaluator evaluator = new Evaluator(measures);
for (Object formula : formulae) {
    JsonObject jsonObj = (JsonObject) formula;
    evaluator.formula(jsonObj.get("formula").getAsString());
    Double metricVal = Double.parseDouble(evaluator.evaluate().toString());
    // do stuff with value
}

Код работает, как и ожидалось. Я хочу знать, делаю ли я что-то неправильно, что может повлиять на программу в долгосрочной перспективе / если что-то не является наилучшей практикой / если есть лучший способ / более простой способ сделать то же самое.

Этот вопростесно связанный с вопросом , который я отправил вчера. Вроде вторая часть.

...