создание простого механизма правил в java

Я изучаю различные способы создания простого механизма бизнес-правил на Java. Мне нужно предоставить клиенту простое веб-приложение, которое позволяет ему настраивать кучу правил. Пример базы правил может выглядеть так:

Вот пример:

 IF (PATIENT_TYPE = "A" AND ADMISSION_TYPE="O")
 SEND TO OUTPATIENT
 ELSE IF PATIENT_TYPE = "B" 
 SEND TO INPATIENT

Механизм правил довольно прост, последним действием может быть только одно из двух действий, отправка в стационар или амбулатор. Операторы, участвующие в выражении, могут быть =,>,<,!=, а логические операторы между выражениями - AND, OR and NOT.

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

Из исследований, которые я провел до сих пор, я наткнулся на ANTLR и написал свой собственный язык сценариев как возможные варианты решения этой проблемы. Я не исследовал такие варианты, как движок правил Drools, потому что чувствую, что это может быть излишним. Был ли у вас опыт решения подобных проблем? Если да, то как вы это сделали?


person Jay    schedule 24.12.2013    source источник
comment
stackoverflow.com/questions/4329858/ может помочь   -  person    schedule 24.12.2013
comment
Кажется излишним тратить время на повторную реализацию того, что уже существует (даже если вы не собираетесь повторно использовать все из того, что доступно).   -  person Joshua Taylor    schedule 24.12.2013
comment
@JoshuaTaylor Меня беспокоят усилия по настройке здесь. Не каждый фреймворк с открытым исходным кодом будет соответствовать моим требованиям, и потребуется небольшая настройка. Иногда время, затрачиваемое на настройку чего-либо, может превышать создание более простой альтернативы.   -  person Jay    schedule 24.12.2013
comment
джентльмену, который потрудился проголосовать против, простой вопрос - не могли бы вы объяснить свои намерения, стоящие за этим благородным поступком, этому посредственному миру !?   -  person Jay    schedule 24.12.2013
comment
@Jay В противовес тому, что говорят многие другие, я думаю, что если вы решите, что ваш DSL будет довольно простым и статичным, реализация его самостоятельно может быть лучше, чем использование тяжелого фреймворка. У повторного изобретения колеса есть и недостатки, но есть и недостатки у включения тяжелых каркасов. Если вы не знакомы с ними, у них есть способы заставить вас заплатить позже. Мне постоянно напоминают об этом, когда я, например, использую различные ORM (cough Hibernate cough). Если вы предоставили полный DSL, вы можете проанализировать правила с помощью FSM.   -  person torbinsky    schedule 03.01.2014
comment
@Jay С ума сойти, вы могли бы: Смоделировать это с помощью конечного автомата. Переход между состояниями для каждого токена в ваших правилах. Если вы встретите токен без перехода из вашего текущего состояния, вы можете предоставить удобное для человека сообщение об ошибке. Для начала вы можете воспользоваться множеством онлайн-примеров для создания чего-то вроде калькулятора постфиксов. По сути, вы должны токенизировать текст, а затем переводить его в эквиваленты оператора Java. Вы можете убедиться в правильности логической структуры, собрав дерево выражений. Для получения дополнительной информации погуглите некоторые ключевые слова, такие как постфикс оценки Java-выражения.   -  person torbinsky    schedule 03.01.2014
comment
@Jay, вы, вероятно, получили отрицательный голос из-за антипаттернов внутренней платформы / программного кодирования thedailywtf.com/Articles/Soft_Coding.aspx - это лучше всего резюмируется в предложении Хм, у меня есть молоток, теперь я могу создать инструмент, чтобы забить несколько гвоздей, и многие советы, которые вы получили здесь, позволяют с полным правом использовать свои язык программирования для моделирования вашей бизнес-логики thedailywtf.com/Articles/The-Mythical-Business -Layer.aspx   -  person bbozo    schedule 05.01.2014
comment
Я расширил свой последний комментарий до полного ответа, чтобы лучше понять, что я имею в виду, stackoverflow.com/questions/20763189/   -  person bbozo    schedule 05.01.2014


Ответы (15)


Реализовать в Java простую систему оценки, основанную на правилах, не так уж и сложно. Наверное, синтаксический анализатор выражения - самая сложная вещь. В приведенном ниже примере кода используется несколько шаблонов для достижения желаемой функциональности.

Одноэлементный шаблон используется для хранения каждой доступной операции в карте элементов. Сама операция использует шаблон команды для обеспечения гибкой расширяемости, в то время как соответствующее действие для допустимого выражения действительно использует шаблон диспетчеризации. И, наконец, что не менее важно, для проверки каждого правила используется шаблон интерпретатора.

Выражение, подобное представленному в вашем примере выше, состоит из операций, переменных и значений. Что касается примера вики, все, что может быть объявлено, является Expression. Таким образом, интерфейс выглядит так:

import java.util.Map;

public interface Expression
{
    public boolean interpret(final Map<String, ?> bindings);
}

Хотя пример на вики-странице возвращает int (они реализуют калькулятор), нам нужно только логическое возвращаемое значение здесь, чтобы решить, должно ли выражение запускать действие, если выражение оценивается как true.

Выражение может, как указано выше, быть операцией типа =, AND, NOT, ... или Variable или его Value. Определение Variable приведено ниже:

import java.util.Map;

public class Variable implements Expression
{
    private String name;

    public Variable(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return this.name;
    }

    @Override
    public boolean interpret(Map<String, ?> bindings)
    {
        return true;
    }
}

Проверка имени переменной не имеет особого смысла, поэтому по умолчанию возвращается true. То же самое верно и для значения переменной, которая сохраняется как можно более обобщенной при определении только BaseType:

import java.util.Map;

public class BaseType<T> implements Expression
{
    public T value;
    public Class<T> type;

    public BaseType(T value, Class<T> type)
    {
        this.value = value;
        this.type = type;
    }

    public T getValue()
    {
        return this.value;
    }

    public Class<T> getType()
    {
        return this.type;
    }

    @Override
    public boolean interpret(Map<String, ?> bindings)
    {
        return true;
    }

    public static BaseType<?> getBaseType(String string)
    {
        if (string == null)
            throw new IllegalArgumentException("The provided string must not be null");

        if ("true".equals(string) || "false".equals(string))
            return new BaseType<>(Boolean.getBoolean(string), Boolean.class);
        else if (string.startsWith("'"))
            return new BaseType<>(string, String.class);
        else if (string.contains("."))
            return new BaseType<>(Float.parseFloat(string), Float.class);
        else
            return new BaseType<>(Integer.parseInt(string), Integer.class);
    }
}

Класс BaseType содержит фабричный метод для генерации конкретных типов значений для определенного типа Java.

Operation теперь является специальным выражением, таким как AND, NOT, =, ... Абстрактный базовый класс Operation действительно определяет левый и правый операнды, поскольку операнд может относиться к более чем одному выражению. F.e. NOT, вероятно, относится только к своему правому выражению и отрицает результат проверки, поэтому true превращается в false и наоборот. Но AND с другой стороны логически объединяет левое и правое выражения, заставляя оба выражения быть истинными при проверке.

import java.util.Stack;

public abstract class Operation implements Expression
{
    protected String symbol;

    protected Expression leftOperand = null;
    protected Expression rightOperand = null;

    public Operation(String symbol)
    {
        this.symbol = symbol;
    }

    public abstract Operation copy();

    public String getSymbol()
    {
        return this.symbol;
    }

    public abstract int parse(final String[] tokens, final int pos, final Stack<Expression> stack);

    protected Integer findNextExpression(String[] tokens, int pos, Stack<Expression> stack)
    {
        Operations operations = Operations.INSTANCE;

        for (int i = pos; i < tokens.length; i++)
        {
            Operation op = operations.getOperation(tokens[i]);
            if (op != null)
            {
                op = op.copy();
                // we found an operation
                i = op.parse(tokens, i, stack);

                return i;
            }
        }
        return null;
     }
}

Две операции, наверное, бросаются в глаза. int parse(String[], int, Stack<Expression>); перестраивает логику синтаксического анализа конкретной операции на соответствующий класс операции, поскольку он, вероятно, лучше всех знает, что ему нужно для создания экземпляра допустимой операции. Integer findNextExpression(String[], int, stack); используется для поиска правой части операции при преобразовании строки в выражение. Может показаться странным возвращать здесь int вместо выражения, но выражение помещается в стек, а возвращаемое значение здесь просто возвращает позицию последнего токена, используемого созданным выражением. Таким образом, значение int используется для пропуска уже обработанных токенов.

Операция AND выглядит так:

import java.util.Map;
import java.util.Stack;

public class And extends Operation
{    
    public And()
    {
        super("AND");
    }

    public And copy()
    {
        return new And();
    }

    @Override
    public int parse(String[] tokens, int pos, Stack<Expression> stack)
    {
        Expression left = stack.pop();
        int i = findNextExpression(tokens, pos+1, stack);
        Expression right = stack.pop();

        this.leftOperand = left;
        this.rightOperand = right;

        stack.push(this);

        return i;
    }

    @Override
    public boolean interpret(Map<String, ?> bindings)
    {
        return leftOperand.interpret(bindings) && rightOperand.interpret(bindings);
    }
}

В parse вы, вероятно, видите, что уже сгенерированное выражение с левой стороны берется из стека, затем правая часть анализируется и снова берется из стека, чтобы, наконец, протолкнуть новую операцию AND, содержащую как левое, так и правое выражение, обратно в стек.

NOT в этом случае аналогичен, но устанавливает только правую часть, как описано ранее:

import java.util.Map;
import java.util.Stack;

public class Not extends Operation
{    
    public Not()
    {
        super("NOT");
    }

    public Not copy()
    {
        return new Not();
    }

    @Override
    public int parse(String[] tokens, int pos, Stack<Expression> stack)
    {
        int i = findNextExpression(tokens, pos+1, stack);
        Expression right = stack.pop();

        this.rightOperand = right;
        stack.push(this);

        return i;
    }

    @Override
    public boolean interpret(final Map<String, ?> bindings)
    {
        return !this.rightOperand.interpret(bindings);
    }    
}

Оператор = используется для проверки значения переменной, если оно действительно равно определенному значению в карте привязок, предоставленной в качестве аргумента в методе interpret.

import java.util.Map;
import java.util.Stack;

public class Equals extends Operation
{      
    public Equals()
    {
        super("=");
    }

    @Override
    public Equals copy()
    {
        return new Equals();
    }

    @Override
    public int parse(final String[] tokens, int pos, Stack<Expression> stack)
    {
        if (pos-1 >= 0 && tokens.length >= pos+1)
        {
            String var = tokens[pos-1];

            this.leftOperand = new Variable(var);
            this.rightOperand = BaseType.getBaseType(tokens[pos+1]);
            stack.push(this);

            return pos+1;
        }
        throw new IllegalArgumentException("Cannot assign value to variable");
    }

    @Override
    public boolean interpret(Map<String, ?> bindings)
    {
        Variable v = (Variable)this.leftOperand;
        Object obj = bindings.get(v.getName());
        if (obj == null)
            return false;

        BaseType<?> type = (BaseType<?>)this.rightOperand;
        if (type.getType().equals(obj.getClass()))
        {
            if (type.getValue().equals(obj))
                return true;
        }
        return false;
    }
}

Как видно из метода parse, переменной присваивается значение, при этом переменная находится слева от символа =, а значение - справа.

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

Поскольку фактический синтаксический анализ выражений преобразован в операции, фактический синтаксический анализатор довольно тонкий:

import java.util.Stack;

public class ExpressionParser
{
    private static final Operations operations = Operations.INSTANCE;

    public static Expression fromString(String expr)
    {
        Stack<Expression> stack = new Stack<>();

        String[] tokens = expr.split("\\s");
        for (int i=0; i < tokens.length-1; i++)
        {
            Operation op = operations.getOperation(tokens[i]);
            if ( op != null )
            {
                // create a new instance
                op = op.copy();
                i = op.parse(tokens, i, stack);
            }
        }

        return stack.pop();
    }
}

Здесь, наверное, самое интересное - метод copy. Поскольку синтаксический анализ носит довольно общий характер, мы не знаем заранее, какая операция в настоящее время обрабатывается. При возврате найденной операции среди зарегистрированных приводит к модификации этого объекта. Если у нас есть только одна операция такого типа в нашем выражении, это не имеет значения - однако, если у нас есть несколько операций (например, две или более операций равенства), операция используется повторно и, следовательно, обновляется с новым значением. Поскольку это также изменяет ранее созданные операции такого типа, нам необходимо создать новый экземпляр операции - copy() этого достигает.

Operations - это контейнер, который содержит ранее зарегистрированные операции и отображает операцию на указанный символ:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public enum Operations
{
    /** Application of the Singleton pattern using enum **/
    INSTANCE;

    private final Map<String, Operation> operations = new HashMap<>();

    public void registerOperation(Operation op, String symbol)
    {
        if (!operations.containsKey(symbol))
            operations.put(symbol, op);
    }

    public void registerOperation(Operation op)
    {
        if (!operations.containsKey(op.getSymbol()))
            operations.put(op.getSymbol(), op);
    }

    public Operation getOperation(String symbol)
    {
        return this.operations.get(symbol);
    }

    public Set<String> getDefinedSymbols()
    {
        return this.operations.keySet();
    }
}

Помимо одноэлементного шаблона enum, здесь нет ничего особенного.

Rule теперь содержит одно или несколько выражений, которые при оценке могут запускать определенное действие. Следовательно, правило должно содержать ранее проанализированные выражения и действие, которое должно быть запущено в случае успеха.

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Rule
{
    private List<Expression> expressions;
    private ActionDispatcher dispatcher;

    public static class Builder
    {
        private List<Expression> expressions = new ArrayList<>();
        private ActionDispatcher dispatcher = new NullActionDispatcher();

        public Builder withExpression(Expression expr)
        {
            expressions.add(expr);
            return this;
        }

        public Builder withDispatcher(ActionDispatcher dispatcher)
        {
            this.dispatcher = dispatcher;
            return this;
        }

        public Rule build()
        {
            return new Rule(expressions, dispatcher);
        }
    }

    private Rule(List<Expression> expressions, ActionDispatcher dispatcher)
    {
        this.expressions = expressions;
        this.dispatcher = dispatcher;
    }

    public boolean eval(Map<String, ?> bindings)
    {
        boolean eval = false;
        for (Expression expression : expressions)
        {
            eval = expression.interpret(bindings);
            if (eval)
                dispatcher.fire();
        }
        return eval;
    }
}

Здесь шаблон здания используется только для того, чтобы можно было добавить несколько выражений, если это необходимо для одного и того же действия. Кроме того, Rule по умолчанию определяет NullActionDispatcher. Если выражение оценено успешно, диспетчер запустит метод fire(), который обработает действие, которое должно быть выполнено при успешной проверке. Шаблон NULL используется здесь, чтобы избежать работы с нулевыми значениями в случае, если выполнение действий не требуется, поскольку должна выполняться только проверка true или false. Таким образом, интерфейс также прост:

public interface ActionDispatcher
{
    public void fire();
}

Поскольку я действительно не знаю, какими должны быть ваши INPATIENT или OUTPATIENT действия, метод fire() запускает только вызов метода System.out.println(...);:

public class InPatientDispatcher implements ActionDispatcher
{
    @Override
    public void fire()
    {
        // send patient to in_patient
        System.out.println("Send patient to IN");
    }
}

И последнее, но не менее важное: простой основной метод для проверки поведения кода:

import java.util.HashMap;
import java.util.Map;

public class Main 
{
    public static void main( String[] args )
    {
        // create a singleton container for operations
        Operations operations = Operations.INSTANCE;

        // register new operations with the previously created container
        operations.registerOperation(new And());
        operations.registerOperation(new Equals());
        operations.registerOperation(new Not());

        // defines the triggers when a rule should fire
        Expression ex3 = ExpressionParser.fromString("PATIENT_TYPE = 'A' AND NOT ADMISSION_TYPE = 'O'");
        Expression ex1 = ExpressionParser.fromString("PATIENT_TYPE = 'A' AND ADMISSION_TYPE = 'O'");
        Expression ex2 = ExpressionParser.fromString("PATIENT_TYPE = 'B'");

        // define the possible actions for rules that fire
        ActionDispatcher inPatient = new InPatientDispatcher();
        ActionDispatcher outPatient = new OutPatientDispatcher();

        // create the rules and link them to the accoridng expression and action
        Rule rule1 = new Rule.Builder()
                            .withExpression(ex1)
                            .withDispatcher(outPatient)
                            .build();

        Rule rule2 = new Rule.Builder()
                            .withExpression(ex2)
                            .withExpression(ex3)
                            .withDispatcher(inPatient)
                            .build();

        // add all rules to a single container
        Rules rules = new Rules();
        rules.addRule(rule1);
        rules.addRule(rule2);

        // for test purpose define a variable binding ...
        Map<String, String> bindings = new HashMap<>();
        bindings.put("PATIENT_TYPE", "'A'");
        bindings.put("ADMISSION_TYPE", "'O'");
        // ... and evaluate the defined rules with the specified bindings
        boolean triggered = rules.eval(bindings);
        System.out.println("Action triggered: "+triggered);
    }
}

Rules здесь просто простой контейнерный класс для правил, распространяющий вызов eval(bindings); на каждое определенное правило.

Я не включаю другие операции, так как публикация здесь уже слишком длинная, но не должно быть слишком сложно реализовать их самостоятельно, если вы того пожелаете. Кроме того, я не включил свою структуру пакета, так как вы, вероятно, будете использовать свою собственную. Более того, я не включил никакой обработки исключений, я оставляю это на усмотрение всех, кто собирается скопировать и вставить код :)

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

Вместо использования системы на основе правил сеть Петри или даже BPMN в сочетании с открытым исходным кодом < href = "http://activiti.org/" rel = "noreferrer"> Activiti Engine может выполнить эту задачу. Здесь операции уже определены в языке, вам нужно только определить конкретные операторы как задачи, которые могут выполняться автоматически - и в зависимости от результата задачи (то есть отдельного оператора) он продолжит свой путь через «граф» . Поэтому моделирование обычно выполняется в графическом редакторе или во внешнем интерфейсе, чтобы не иметь дело с XML-природой языка BPMN.

person Roman Vottner    schedule 02.01.2014
comment
хотя я обнаружил, что другие ответы так же полезны, как и ваш, и, вероятно, это не тот путь, который я собираюсь использовать, чтобы решить свою проблему, я награждаю его вашим ответом в основном потому, что он сложен и показывает, что вы потратили много времени и энергии, чтобы собрать все воедино. Спасибо! - person Jay; 05.01.2014
comment
Это, вероятно, самый подробный ответ, который я видел на SO. Молодец. - person medopal; 11.06.2015

В основном ... Не делай этого

Чтобы понять, почему см.:

  1. http://thedailywtf.com/Articles/The_Customer-Friendly_System.aspx
  2. http://thedailywtf.com/Articles/The-Mythical-Business-Layer.aspx
  3. http://thedailywtf.com/Articles/Soft_Coding.aspx

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

Я лично прошел по этому пути в бывшей фирме, и я видел, к чему это приводит через пару лет (гигантские необнаруживаемые скрипты, хранящиеся в базе данных, написанные на языке, пришедшем прямо из параллельного измерения, где Бог ненавидит нас которые, в конце концов, никогда не соответствуют ожиданиям клиентов на 100%, потому что они не так мощны, как правильный язык программирования, и в то же время они слишком запутаны и вредны для разработчиков (не говоря уже о клиенте) ).

Я знаю, что есть определенный тип клиентов, которым нравится идея, что они не будут платить часы программисту за «адаптацию бизнес-правил», и мало понимают, что в конечном итоге им будет хуже, и чтобы привлечь такого клиента, вы нужно что-то делать в этом направлении - но что бы вы ни делали, не изобретайте ничего своего.

Существует множество достойных языков сценариев, которые поставляются с хорошими инструментами (которые не требуют компиляции, поэтому могут быть загружены динамически и т. Д.), Которые могут быть гладко связаны и вызваны из кода Java и использовать преимущества ваших реализованных API Java, которые вы делаете доступны, см. http://www.slideshare.net/jazzman1980/j-ruby-injavapublic#btnNext например, возможно, Jython тоже,

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

person bbozo    schedule 05.01.2014
comment
Я набросился на описание скриптов. +1 - person flup; 05.01.2014
comment
Я знаю, что есть определенный тип клиентов, которым нравится идея, что они не будут платить часы программисту за «адаптацию бизнес-правил». Я разработчик. Моя работа - давать хорошие технические рекомендации. Вот как я бы сформулировал вашу рекомендацию в этом ответе: это решение исторически оказалось менее надежным и более подверженным ошибкам. В долгосрочной перспективе вряд ли удастся сэкономить усилия (и, следовательно, деньги). Это займет больше времени и приведет к большему количеству ошибок. Последнее предложение означает больше времени (и денег на это время) и больше долларов поддержки. - person jpmc26; 27.03.2015

Я бы посоветовал использовать что-то вроде Drools. Создание собственного пользовательского решения было бы излишним, потому что вам пришлось бы его отлаживать, и при этом обеспечивать функциональность, безусловно, меньшую, чем та, которая предоставляется механизмом правил, таким как Drools. Я понимаю, что у Drools есть кривая обучения, но я бы не сравнивал это с созданием собственного языка или нестандартного решения ...

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

Надеюсь, я помог!

person Pantelis Natsiavas    schedule 24.12.2013
comment
Я читал документацию Drools. Я до сих пор не уверен, как попросить пользователя написать правило Drools - это не то, что бы понял бизнес-пользователь. Как сделать это простым для конечного пользователя? - person Jay; 24.12.2013
comment
Как мне упростить задачу для конечного пользователя - Мы столкнулись с аналогичной проблемой - мы создали электронную таблицу с шаблоном (таблица решений), которую пользователь заполнил своими критериями правила, а затем преобразовал ее в формальные правила для слюни. См. docs.jboss. org / drools / release / 5.2.0.Final / drools-expert-docs / для получения дополнительных сведений и примера. - person Candyfloss; 02.01.2014

Исходя из прошлого опыта, решение, основанное на правилах «обычного текста», является ОЧЕНЬ плохой идеей, оно оставляет много места для ошибок, а также, как только вам придется добавить несколько простых или сложных правил, его код / ​​код станет кошмаром. отлаживать / поддерживать / изменять ...

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

На стороне веб-интерфейса или внешнего интерфейса вы создадите компонент (для каждой реализации правила), который строго соответствует этому правилу. Затем вы можете предоставить пользователю возможность выбора того правила, которое он хотел бы использовать, и соответствующим образом обновить интерфейс (путем перезагрузки страницы / javascript).

Когда правило будет добавлено / изменено, выполните итерацию по всем реализациям правила, чтобы получить соответствующую реализацию и чтобы эта реализация проанализировала необработанные данные (id рекомендует использовать json) из внешнего интерфейса, затем выполните это правило.

public abstract class AbstractRule{
  public boolean canHandle(JSONObject rawRuleData){
    return StringUtils.equals(getClass().getSimpleName(), rawRuleData.getString("ruleClassName"));
  }
  public abstract void parseRawRuleDataIntoThis(JSONObject rawRuleData); //throw some validation exception
  public abstract RuleResult execute();
}
public class InOutPatientRule extends AbstractRule{
  private String patientType;
  private String admissionType;

  public void parseRawRuleDataIntoThis(JSONObject rawRuleData){
    this.patientType = rawRuleData.getString("patientType");
    this.admissionType= rawRuleData.getString("admissionType");
  }
  public RuleResultInOutPatientType execute(){
    if(StringUtils.equals("A",this.patientType) && StringUtils.equals("O",this.admissionType)){
      return //OUTPATIENT
    }
    return //INPATIENT
  }
}
person NeilA    schedule 02.01.2014
comment
Я полностью согласен с тем, что использование текстового поля произвольной формы будет подвержено ошибкам и действительно усложнит обработку ошибок. Было бы неплохо использовать компоненты, упомянутые в @NeilA. У вас все еще может быть текстовая область или что-то в этом роде для вывода окончательного выражения, если вы хотите отобразить его пользователю в текстовой форме. Хотя, наверное, лучше не позволять им редактировать это. - person torbinsky; 03.01.2014

Вы настраиваете себя на неудачу по двум основным причинам:

  1. Разбирать произвольный текст от пользователя - ТРУДНО.
  2. Написание парсеров на Java несколько громоздко

Решение 1. либо подтолкнет вас к нечеткой области НЛП, для которой вы можете использовать такой инструмент, как OpenNLP, или что-то из этой экосистемы. Из-за большого количества тонко различных способов, которыми пользователь может что-то записывать, вы обнаружите, что ваше мышление смещено в сторону более формальной грамматики. Выполнение этой работы приведет вас к решению типа DSL, или вам придется разработать свой собственный язык программирования.

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

В итоге, даже если вы думаете об очень простом языке правил, вы обнаружите, что недооцениваете количество сценариев, которые вам нужно проверить. NeilA права, посоветовав вам уменьшить сложность, создав соответствующий пользовательский интерфейс для каждого типа правил. Не пытайтесь быть слишком общим, иначе это взорвется вам в лицо.

person iwein    schedule 02.01.2014

Если вы ищете что-то более легкое, чем пускает слюни, но с аналогичной функциональностью, вы можете проверить проект http://smartparam.org/ . Это позволяет сохранять параметры в файлах свойств, а также в базе данных.

person Jakub Kubrynski    schedule 04.01.2014

Вместо того, чтобы создавать свой собственный механизм правил, вы можете рассмотреть механизм N-CUBE с открытым исходным кодом, механизм правил Java с открытым исходным кодом, который использует Groovy в качестве предметно-ориентированного языка (DSL).

Это механизм последовательных правил, в отличие от механизма непоследовательных правил, такого как механизм правил на основе RETE. Преимущество механизма последовательных правил состоит в том, что правила намного проще отлаживать. Попытка расшифровать выводы из действительно больших наборов правил может быть очень сложной, но с последовательным механизмом правил, таким как N-CUBE, отслеживание правил очень похоже на следование последовательной «логике кода».

N-CUBE имеет встроенную поддержку таблиц решений и деревьев решений. Таблицы решений и деревья в N-CUBE позволяют выполнять данные или код в ячейках, что очень похоже на многомерный Excel. "Макро" язык (DSL) - Groovy. При написании кода внутри ячейки вам не нужно определять оператор пакета, импорт, имя класса или функцию - все это добавляется за вас, что упрощает чтение / запись фрагментов кода DSL.

Этот механизм правил доступен на GitHub по адресу https://github.com/jdereg/n-cube. .

person John DeRegnaucourt    schedule 30.07.2015

Вместо textArea используйте provide как поле выбора для фиксированного состояния (PATIENT_TYPE) и фиксированных операторов (), и все будет готово. В любом случае вы контролируете, как выглядит веб-приложение.

person Sathya    schedule 24.12.2013

На основе замыканий можно построить простой механизм правил, например, в Groovy:

def sendToOutPatient = { ... };

def sendToInPatient = { ... };

def patientRule = { PATIENT_TYPE ->
    {'A': sendToOutPatient,
     'B': sendToInPatient}.get(PATIENT_TYPE)
}

static main(){
    (patientRule('A'))()
}

Вы можете определить свои правила как закрытие, повторно использовать / переназначить их или даже построить над ними DSL.

И Groovy можно легко встроить в Java, например:

GroovyShell shell = new GroovyShell(binding);
binding.setVariable("foo", "World");
System.out.println(shell.evaluate("println 'Hello ${foo}!';));
person Andrey Chaschev    schedule 24.12.2013
comment
спасибо за ваш пример, но я не думаю, что он решает мою проблему. Моя цель - дать бизнес-пользователю простой способ определять правила. Если еще и или нет, и математические операторы - это всего лишь область. - person Jay; 24.12.2013
comment
Drools смотрится эффектно. Тем не менее, я бы посоветовал @Jay тщательно изучить его и найти время, чтобы понять его, прежде чем совершать. Я видел много примеров, когда разработчики закрашивали себя в углы с помощью подобных фреймворков. Все начинается отлично, ваш простой пример использования работает. Через несколько месяцев ваш клиент вчера что-то хотел, но вы застряли в лабиринте фреймворка. При этом я также большой поклонник того, чтобы не изобретать колесо заново. Возникает вопрос: Drools - это колесо или это реактивный двигатель, а OP действительно нужно только резиновое колесо? - person torbinsky; 03.01.2014
comment
@Jay JIC, вы считаете полезным, пример Groovy DSL: Log4j DSL. Мартин Фаулер - сторонник DSL и свободных интерфейсов, он даже написал о них книгу . - person Andrey Chaschev; 03.01.2014

Существует механизм правил для Clojure под названием Clara, который можно использовать как из java, так и из Clojure [Java] Script. Я думаю, что из этого было бы довольно легко создать что-то полезное.

person claj    schedule 05.01.2014

Я бы так и поступил. Создаю набор переменных регулярного выражения, в зависимости от совпадения кодирую бизнес-логику. Если набор правил будет более сложным, чем этот, я бы выбрал реализацию apache commons CommandLineParser на сервере.

Но вы можете использовать графический интерфейс / HTML и набор раскрывающихся и дополнительных раскрывающихся списков. Таким образом вы сможете четко выполнять запросы к базе данных.

person Siva Tumma    schedule 01.01.2014

Хорошо поговорите со своими пользователями, спросив их, почему это нужно настраивать, и какие изменения в конфигурации они ожидают в будущем. Узнайте, какие грядущие изменения являются определенными, вероятными, отдаленно возможными, невероятно маловероятными. И как быстро они должны быть реализованы. Будет ли приемлемо написание небольшого выпуска обновления для каждого изменения?

Помня о такой степени гибкости, оцените вариант развертывания собственного решения по сравнению с вариантом включения полноценного движка. «Протестируйте» свое простое решение в соответствии со сценариями предстоящих изменений, кратко запишите, как каждое изменение будет реализовано. Ничего страшного, если некоторые маловероятные сценарии обходятся дорого. Однако, если вероятные сценарии слишком дороги, вам лучше выбрать более общее решение.

Что касается вариантов, которые следует рассмотреть, мне нравятся как слюни, так и предложение написать свое собственное. Третий вариант: при внедрении пакета финансовой регистрации с ежегодным обновлением законодательства мы довольно успешно реализовали правила в коде, но оставили их параметры настраиваемыми в таблицах sql. Итак, в вашем случае это может означать таблицу примерно так:

patient_type | admission_type | inpatient_or_outpatient
-------------------------------------------------------
'A'          | 'O'            | 'Outpatient'
'B'          | NULL           | 'Inpatient'

(В наших таблицах, как правило, есть столбцы даты начала и окончания действия, которые позволяют пользователю вносить изменения)

Если вы в конечном итоге напишете DSL, взгляните на http://martinfowler.com/books/dsl.html с подробным описанием нескольких подходов. В качестве предостережения: в своем разделе вопросов и ответов Мартин Фаулер пишет:

Так вот в чем прикол - деловые люди сами пишут правила?

В общем, я так не считаю. Это большая работа, чтобы создать среду, позволяющую деловым людям писать свои собственные правила. Вы должны сделать удобный инструмент редактирования, инструменты отладки, инструменты тестирования и так далее. Вы получаете наибольшую выгоду от бизнеса, сталкивающегося с DSL, если делаете достаточно, чтобы позволить деловым людям читать правила. Затем они могут проверить их на точность, обсудить их с разработчиками и спроектировать изменения, которые разработчики должны правильно реализовать. Сделать DSL удобочитаемыми для бизнеса - гораздо меньше усилий, чем для бизнес-записи, но дает большую часть преимуществ. Бывают случаи, когда стоит приложить усилия, чтобы сделать DSL доступными для бизнес-записи, но это более продвинутая цель.

person flup    schedule 05.01.2014

Поскольку синтаксический анализ кода только с помощью Java является самоубийством реализации, вы можете написать простой компилятор, используя Jflex и CUP, которые являются Java-версией GNU FLEX и YACC. Таким образом, вы можете сгенерировать простые токены с Jflex (токен - это такое ключевое слово, как IF, ELSE и т. Д.), В то время как CUP будет использовать эти токены для выполнения некоторого кода.

person HAL9000    schedule 05.01.2014

Реализовать механизм правил нет тривиальной задачи. Осмысленная система, основанная на правилах, имеет механизм вывода, который поддерживает как прямую, так и обратную цепочки, а также стратегии поиска в ширину и в глубину. В Easy Rules ничего этого нет, он просто выполняет все правила один раз и только один раз. Drools поддерживает прямую и обратную цепочки, а afaik также поддерживает сначала глубину, а сначала ширину. Это объясняется здесь .

По моему опыту, Drools - единственный значимый движок правил для java. У него есть свои ограничения. Я должен сказать, что использовал Drools 5+ лет назад.

person Christine    schedule 06.07.2015

Еще одно дополнение к этому списку - http://openl-tablets.org/. Он позволяет вам определять правила в Excel, загружать их в веб-редактор. Он очень гибкий и имеет встроенную возможность запускать тесты по правилам с некоторыми данными.

person Community    schedule 28.08.2020