création d'un moteur de règles simple en java

j'explore différentes façons de créer un simple moteur de règles d'affaires en Java. Je dois présenter au client une webapp simple qui lui permet de configurer un tas de règles. Un exemple de base de règles pourrait ressembler à ceci:

voici un exemple:

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

le moteur de la règle est assez simple, l'action finale pourrait n'être qu'une de deux actions, l'envoi aux patients hospitalisés ou externes. Les opérateurs impliqués dans une expression peut être =,>,<,!= et opérateurs logiques entre les expressions sont AND, OR and NOT.

je veux créer une application web où l'utilisateur va écrire un petit script dans un textarea, et je voudrais évaluer l'expression - de cette manière, les règles métier sont expliqué dans un anglais simple et l'utilisateur a le contrôle complet sur la logique.

D'après les recherches que j'ai faites jusqu'à présent, je suis tombé sur, ANTLR et écrire mon propre langage de script comme options possibles pour résoudre ce problème. Je n'ai pas exploré d'options comme le moteur de règles Drools, parce que j'ai un sentiment qu'il serait peut-être trop ici. Avez-vous une expérience dans la résolution de ce genre de problèmes? Si oui, comment avez-vous procédé?

25
demandé sur Nathan Hughes 2013-12-24 19:26:28

14 réponses

implémenter un système d'évaluation simple basé sur des règles en Java n'est pas si difficile à réaliser. Probablement l'analyseur pour l'expression est la chose la plus compliquée. Le code d'exemple ci-dessous utilise quelques modèles pour obtenir la fonctionnalité désirée.

un modèle singleton est utilisé pour stocker chaque opération disponible dans une carte membre. L'opération elle-même utilise un modèle de commande pour fournir l'extensibilité flexible tandis que l'action respective pour une expression valide fait usage de le schéma d'expédition. Dernier buste pas moins, un modèle d'interprète est utilisé pour valider chaque règle.

une expression telle que présentée dans votre exemple ci-dessus se compose d'opérations, de variables et de valeurs. En référence à un wiki-exemple tout ce qui peut être déclarée est un Expression. L'interface, par conséquent, ressemble à ceci:

import java.util.Map;

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

alors que l'exemple sur la page wiki renvoie un int (ils implémentent une calculatrice), nous n'avons besoin que d'un retour booléen valeur ici de décider si une expression doit déclencher une action si l'expression est évaluée à true.

Une expression peut, comme indiqué ci-dessus, soit d'une opération comme =,AND,NOT,... ou un Variable ou de ses Value. La définition d'un Variable est enrôlé ci-dessous:

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;
    }
}

Validation d'un nom de variable ne fait pas beaucoup de sens, donc true est retourné par défaut. Il en va de même pour une valeur d'une variable qui est conservée aussi générique que possible sur la définition d'un BaseType seulement:

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 class contient une méthode d'usine pour générer des types de valeurs concrètes pour un type Java spécifique.

Operation est maintenant une expression spéciale comme AND,NOT,=,... La classe de base abstraite Operation définit un à gauche et à droite opérande opérande peut se référer à plus d'une expression. F. E. NOT se réfère probablement seulement à son expression de la main droite et nie son validation du résultat, donc true transformer en false et vice versa. Mais AND d'un autre côté, combine logiquement une expression de gauche et de droite, forçant les deux expressions à être vraies lors de la validation.

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;
     }
}

deux opérations sautent probablement dans l'œil. int parse(String[], int, Stack<Expression>); refacteurs la logique de l'analyse de l'opération concrète à la classe opératoire respective car elle sait probablement mieux ce dont elle a besoin pour instancier une opération valide. Integer findNextExpression(String[], int, stack); est utilisé pour trouver le côté droit de la opération lors de l'analyse de la chaîne dans une expression. Il peut sembler étrange de retourner un int ici au lieu d'une expression mais l'expression est poussée sur la pile et la valeur de retour ici retourne juste la position du dernier jeton utilisé par l'expression créée. Ainsi, la valeur int est utilisée pour sauter des jetons déjà traités.

AND opération ne ressembler à ceci:

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 vous voyez probablement que l'expression déjà générée de le côté gauche est prise à partir de la pile, puis le côté droit est analysé et repris à partir de la pile pour enfin pousser la nouvelle AND opération contenant à la fois l'expression de la main gauche et celle de la main droite, sur la pile.

NOT est similaire dans ce cas, mais seul le côté droit, comme décrit précédemment:

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);
    }    
}

= l'opérateur est utilisé pour vérifier la valeur d'une variable si elle égale en fait une valeur spécifique dans la table de liaisons fournie comme argument dans le interpret méthode.

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;
    }
}

comme on peut le voir de la parse méthode une valeur est affectée à une variable avec la variable sur le côté gauche de la = symbole et la valeur sur le côté droit.

par Ailleurs, l'interprétation vérifie la disponibilité de la variable nom de la variable liaisons. Si elle n'est pas disponible, nous savons que ce terme ne peut pas évaluer vrai, alors nous pouvons ignorer le processus d'évaluation. Si elle est présente, nous avons extrait les informations du côté droit (=Partie valeur) et vérifiez d'abord si le type de classe est égal et si oui si la valeur de la variable réelle correspond à la reliure.

Comme l'analyse des expressions est remaniée dans les opérations, le parser est plutôt mince:

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();
    }
}

voici le copy méthode est probablement la chose la plus intéressante. Comme l'analyse est plutôt générique, nous ne savons pas à l'avance quelle opération est actuellement traitée. Au retour d'un l'opération trouvée parmi les inscrits résulte en une modification de cet objet. Si nous n'avons qu'une seule opération de ce genre dans notre expression, cela n'a pas d'importance - si toutefois nous avons plusieurs opérations (F. E. deux ou plusieurs opérations égales) l'opération est réutilisée et donc mise à jour avec la nouvelle valeur. Comme cela change aussi des opérations de ce genre déjà créées, nous devons créer une nouvelle instance de l'opération -copy() parvient.

Operations est un conteneur qui conserve les opérations précédemment enregistrées et les mappe à un symbole spécifié:

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();
    }
}

à côté du motif enum singleton, rien de très fantaisiste ici.

Rule contient maintenant une ou plusieurs expressions qui, lors de l'évaluation, peuvent déclencher une certaine action. La règle doit donc contenir les expressions déjà interprétées et l'action qui doit être déclenchée en cas de succès.

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;
    }
}

ici un modèle de bâtiment est utilisé juste pour être possibilité d'ajouter plusieurs expressions si désiré pour la même action. En outre, le Rule définit un NullActionDispatcher par défaut. Si une expression est évaluée avec succès, le dispatcher déclenchera un fire() la méthode, qui traitera l'action qui doit être exécutée sur une validation réussie. Le motif null est utilisé ici pour éviter de traiter avec des valeurs null dans le cas où aucune exécution d'action n'est requise car seulement un true ou false la validation doit être effectuée. L'interface est donc simple trop:

public interface ActionDispatcher
{
    public void fire();
}

Comme je ne sais pas vraiment ce que votre INPATIENT ou OUTPATIENT actions devraient être, le fire() la méthode ne déclenche qu'un System.out.println(...); invocation de la méthode:

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

Dernier point mais non le moindre, une simple méthode main pour tester le comportement du code:

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 voici juste une classe de conteneur simple pour les règles et propage le eval(bindings); invocation de chaque règle définie.

je ne comprend pas d'autres opérations que le poste voici déjà longtemps, mais il ne devrait pas être trop difficile à mettre en œuvre sur votre propre si vous le désirez. De plus, je n'ai pas inclus ma structure de paquets car vous utiliserez probablement votre propre structure. Furhtermore, je ne comprend pas toute la gestion des exceptions, je laisse ça à tout le monde qui va copiez et collez le code :)

on pourrait soutenir que l'analyse devrait évidemment se faire dans l'analyseur plutôt que dans les classes concrètes. Je suis conscient de cela, mais d'un autre côté sur l'ajout de nouvelles opérations vous devez modifier l'analyseur ainsi que la nouvelle opération au lieu d'avoir à toucher une seule classe.

au lieu d'utiliser un système basé sur des règles un petri net ou même un BPMN en combinaison avec l'open source Or Le Moteur serait possible d'accomplir cette tâche. Ici les opérations sont déjà définies dans la langue, vous avez seulement besoin de définir les énoncés concrets comme des tâches qui peuvent être exécutées automatiquement - et en fonction du résultat d'une tâche (c'est-à-dire l'énoncé unique), elle passera par le "graphe". La modélisation est donc habituellement effectuée dans un éditeur graphique ou un frontend pour éviter de traiter la nature XML du langage BPMN.

88
répondu Roman Vottner 2014-07-05 12:29:44

en gros... Ne pas le faire

Pour comprendre pourquoi le voir:

  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

je sais que cela ressemble à une grande idée de loin, mais le moteur des règles d'affaires sera invariablement plus difficile de maintenir, déployer et déboguer que le langage de programmation dans lequel il a été écrit - ne pas faire votre propre langages de programmation si vous pouvez l'aider.

j'ai personnellement suivi cette voie dans une firme d'ex et j'ai vu où elle allait après quelques années (des scripts Géants inviolables assis dans une base de données écrite dans un langage qui vient directement d'une dimension parallèle où Dieu nous hait qu'à la fin ne répondent jamais à 100% de l'attente du client parce qu'ils ne sont pas aussi puissant qu'un bon langage de programmation et en même temps beaucoup trop alambiqué et diabolique pour que les devs s'en occupent (peu importe le client)).

je sais qu'il y a un certain type de client qui est amoureux de l'idée qu'ils ne paieront pas les heures de programmeur pour les "adaptations des règles d'affaires" et peu comprendre qu'ils seront plus mauvais à la fin et pour attirer ce genre de client, vous aurez à faire quelque chose dans cette direction - mais ce que vous faites n'inventez pas quelque chose de votre propre.

il y a une pléthore de langages de script convenables qui viennent avec de bons outils (qui ne nécessitent pas de compilation, donc peuvent être téléchargés dynamiquement etc) là-bas qui peuvent être interfacés et appelés à partir du code Java et profiter de votre API Java implémentée que vous mettez à disposition, voir http://www.slideshare.net/jazzman1980/j-ruby-injavapublic#btnNext For example, Jython possibly too,

et lorsque le le client abandonne l'écriture de ces scripts vous se retrouver avec le devoir heureux de maintenir son héritage échoué - assurez-vous que héritage est aussi indolore que possible.

19
répondu bbozo 2014-01-05 18:13:05

je suggère d'utiliser quelque chose comme Bave. Créer votre propre solution personnalisée serait un surmenage parce que vous auriez à le déboguer, et encore fournir des fonctionnalités certainement moins que celle fournie par un moteur de règles comme des bols. Je comprends que les baves ont une courbe d'apprentissage, mais je ne le comparerais pas avec la création d'un langage personnalisé, ou une solution personnalisée...

à mon avis, pour qu'un utilisateur écrive des règles, il doit apprendre quelque chose. Alors que je suppose que vous pourriez prévoir un langage plus simple que le bave règle de la langue, vous ne pourrez jamais saisir tous ses besoins. Le langage des règles des baves serait assez simple pour des règles simples. De plus, vous pourriez lui fournir une documentation bien formée. Si vous désirez contrôler les règles créées par l'utilisateur final et appliquée sur le système, alors il serait peut-être plus judicieux de créer une interface graphique qui forme les règles appliquées sur drools.

J'espère avoir aidé!

10
répondu Pantelis Natsiavas 2013-12-24 18:50:55

D'après l'expérience passée, la solution basée sur des règles" en clair " est une très mauvaise idée, elle laisse beaucoup de place à l'erreur, aussi, dès que vous devez ajouter plusieurs règles simples ou complexes, elle va devenir un cauchemar pour coder/déboguer/maintenir/modifier...

ce que j'ai fait (et cela fonctionne exceptionnellement bien) c'est créer des classes strictes/concrètes qui étendent une règle abstraite (1 pour chaque type de règle). Chaque implémentation sait de quelle information elle a besoin et comment la traiter. informations pour vous obtenir le résultat désiré.

côté web / front-end, vous allez créer un component (pour chaque implémentation de règles) qui correspond strictement à cette règle. Vous pouvez alors donner à l'utilisateur l'option de quelle règle il souhaite utiliser et mettre à jour l'interface en conséquence (par page reload/javascript).

quand la règle est ajoutée / modifiée iterate sur toutes les implémentations de règles pour obtenir l'implémentation correspondante et avoir que l'implémentation analyse les données brutes (id recommandez l'utilisation de json) à partir du front-end, puis exécutez cette règle.

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
  }
}
5
répondu NeilA 2014-01-02 09:19:03

Vous êtes vous-même la mise à l'échec pour deux raisons principales:

  1. analyser le texte libre de l'utilisateur est difficile.
  2. écrire des parsers en Java est quelque peu encombrant

Résolution 1. va soit vous pousser dans le domaine flou de la PNL, pour lequel vous pouvez utiliser un outil comme OpenNLP ou quelque chose de cet écosystème. En raison de la grande quantité de manières subtilement différentes, l'utilisateur peut écrire des choses vers le bas, vous trouverez votre pensée inclinaison vers un grammaire plus formelle. Faire ce travail finira par vous dans une solution de type DSL, ou vous aurez à concevoir votre propre langage de programmation.

j'ai eu des résultats raisonnables en utilisant des combinateurs Scala parser pour analyser à la fois le langage naturel et des grammaires plus formalisées. Les problèmes sont les mêmes, mais le code à écrire pour les résoudre est plus lisible.

bref, même si vous pensez à un langage de règles très simple, vous allez trouver que vous sous-estimez le montant de scénario, vous devez le tester pour. NeilA a raison de vous conseiller de réduire la complexité en créant une UI appropriée pour chaque type de règle. N'essayez pas d'être trop générique, ou il va exploser dans votre visage.

5
répondu iwein 2014-01-02 22:03:09

si vous cherchez quelque chose de plus léger que des bols mais avec des fonctionnalités similaires, vous pouvez vérifier http://smartparam.org/ projet. Il permet de stocker les paramètres dans les fichiers de propriétés ainsi que dans la base de données.

5
répondu Jakub Kubrynski 2014-01-04 15:55:54

plutôt que de construire votre propre moteur de règles, vous pourriez vouloir considérer le moteur de n-CUBE Open Source, un moteur de règles Java Open Source qui utilise Groovy comme langage spécifique au domaine (DSL).

il s'agit d'un moteur de règles séquentielles par opposition à un moteur de règles non séquentielles comme un moteur de règles basé sur le RETE. L'avantage d'un moteur de règles séquentielles est qu'il est très facile de déboguer les règles. Essayer de déchiffrer des inférences à partir de très grands ensembles de règles peut être très difficile, mais avec un moteur de règles séquentielles comme n-CUBE, tracer les règles est très similaire à suivre une "logique de code" séquentielle.

n-CUBE a un support intégré pour les Tables de décision et les arbres de décision. Les tables de décision et les Arbres À L'intérieur du N-CUBE permettent aux données ou au code de s'exécuter à l'intérieur des cellules, tout comme un Excel multidimensionnel. Le langage 'macro' (DSL) est Groovy. Lorsque vous écrivez du code dans une cellule, vous n'avez pas besoin de définir une instruction de paquet, des importations, un nom de classe, ou une fonction - tout cela est ajouté pour vous, ce qui rend les extraits de code DSL faciles à lire / écrire.

Ce moteur de règles est disponible sur GitHub à https://github.com/jdereg/n-cube.

3
répondu John DeRegnaucourt 2015-07-30 05:45:19

au lieu de textArea, provide est une boîte de choix pour l'état fixe(PATIENT_TYPE) et les opérateurs fixes() et vous en aurez fini avec elle. Quoi qu'il en soit, vous contrôlez comment Web app ressemble.

2
répondu Sathya 2013-12-24 15:39:49
def sendToOutPatient = { ... };

def sendToInPatient = { ... };

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

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

vous pourriez définir vos règles comme des fermetures, les réutiliser / réassigner ou même construire une DSL sur elles.

et Groovy peut être facilement intégré dans Java, exemple:

GroovyShell shell = new GroovyShell(binding);
binding.setVariable("foo", "World");
System.out.println(shell.evaluate("println 'Hello ${foo}!';));
2
répondu Andrey Chaschev 2013-12-24 15:41:57

C'est ce que je ferais. Je crée un ensemble de variables regex, en fonction de la correspondance, JE code la logique de l'entreprise. Si l'ensemble de règles devient complexe, j'opterais pour apache commons CommandLineParser implémentation sur le serveur.

mais vous pouvez utiliser GUI / HTML et un ensemble de dropdowns et de sub dropdowns. De cette façon, vous pouvez faire des requêtes de base de données clairement.

2
répondu Siva Tumma 2014-01-06 02:57:21

comme l'analyse du code avec Java est un suicide d'implémentation, vous pouvez écrire un compilateur simple en utilisant Jflex and CUP, qui sont la version Java de GNU FLEX et YACC. De cette façon, vous pouvez générer de simples jetons avec Jflex (un jeton est un mot-clé comme IF,ELSE etc) alors que CUP consommera ces token afin d'exécuter du code.

1
répondu HAL9000 2014-01-05 13:19:04
1
répondu claj 2014-01-05 16:09:49

Avoir une bonne conversation avec vos utilisateurs, de leur demander pourquoi ce doit être configurable, et quels changements dans la configuration qu'ils s'attendent à être à venir. Découvrez ce que les changements à venir sont certains, probables, vaguement possibles, outrageusement improbables. Et la rapidité avec laquelle ils devraient être mis en œuvre. Pour chaque changement, est-ce que la rédaction d'une petite mise à jour serait acceptable ou non?

avec cette flexibilité nécessaire à l'esprit, évaluez l'option de rouler votre propre solution contre que de l'intégration d'un moteur. "Tester" votre solution simple par rapport aux scénarios de changement à venir en écrivant brièvement comment chaque changement serait mis en œuvre. Ce n'est pas grave si certains scénarios invraisemblables ont un coût élevé. Si les scénarios probables sont coûteux aussi, cependant, vous feriez mieux de choisir une solution plus générique.

en ce qui concerne les options à considérer, j'aime les baves et la suggestion d'écrire les vôtres. Une troisième option: lors de la mise en œuvre d'un paquet d'enregistrement financier avec mises à jour légales annuelles, nous avons eu beaucoup de succès en mettant en œuvre les règles en code, mais en laissant leurs paramètres configurables dans les tables sql. Si dans votre cas, cela peut signifier une table quelque chose comme ceci:

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

(nos tables ont tendance à avoir des colonnes date-from et date-to validity qui permettent à l'utilisateur de faire des changements)

si vous finissez par écrire un DSL, jetez un coup d'oeil à http://martinfowler.com/books/dsl.html qui offre des descriptions détaillées des approche. Comme une mise en garde: dans son Q et Une section Martin Fowler écrit:

est - ce que ce sont les gens d'affaires qui écrivent les règles eux-mêmes?

en général, je ne pense pas. C'est beaucoup de travail pour faire un environnement qui permet aux gens d'affaires d'écrire leurs propres règles. Vous avez à faire un outil d'édition confortable, des outils de débogage, des outils de test, et ainsi de suite. Vous obtenez la plupart des avantages de L'entreprise face DSLs par en faire assez pour permettre aux gens d'affaires de lire les règles. Ils peuvent ensuite les examiner pour l'exactitude, parler d'eux avec les développeurs et ébaucher les changements que les développeurs doivent mettre en œuvre correctement. Faire en sorte que les DSL soient affaires lisible est beaucoup moins d'effort que les affaires d'écriture, mais donne la plupart des avantages. Il y a des moments où ça vaut la peine d'être fait l'effort pour rendre le DSLs business-writable, mais il est un plus avancée de l'objectif.

1
répondu flup 2014-01-05 18:30:16

implémenter un moteur de règles est pas trivial. Un système fondé sur des règles significatives a un moteur d'inférence qui soutient à la fois l'enchaînement vers l'avant et l'enchaînement vers l'arrière, ainsi que la largeur d'abord et la profondeur d'abord des stratégies de recherche. Easy Rules n'a rien de tout ça, il exécute toutes les règles une seule fois. Les bols soutiennent l'enchaînement vers l'avant et vers l'arrière, et afaik soutient aussi la profondeur d'abord et la largeur d'abord. C'est expliqué ici.

D'après mon expérience, Drools est le seul moteur de règles significatif pour java. Il a ses limites. Je dois dire que j'ai utilisé des bols il y a plus de 5 ans.

0
répondu Christine 2015-07-06 12:54:34