web-dev-qa-db-fra.com

Une meilleure conception pour une implémentation de moteur de règles

J'ai implémenté un moteur de règles comme celui-ci:

public interface IRuleEngine
{
    List<ValidationMessage> Validate(param1, param2, param3);
}


public class DefaultRuleEngine : IRuleEngine
{
    readonly IList<IRule> _rules;

    public DefaultRuleEngine()
    {

        _rules = new List<IRule>()
        {
            new Rule1(),
            new Rule2(),
            new Rule3(),
            ....
        };
    }

    public List<ValidationMessage> Validate(param1, param2, param3)
    {
        var validationMessageList = new List<ValidationMessage>();

        foreach(var rule in _rules)
        {
            validationMessageList.Add(rule.Validate(param1, param2, param3));
        }

        return validationMessageList;
    }
}

    public interface IRule
{
    ValidationMessage Validate(param1, param2, param3);
}

public class Rule1 : IRule
{
    public ValidationMessage Validate(param1, param2, param3)
    {
        //do logic, return validation message

        return validationMessage;         
    }
}

Maintenant, cela fonctionne bien, mais la maintenance a été fastidieuse. J'utilise le moteur de règles à de nombreux endroits de mon code. J'ai également dû modifier les paramètres nécessaires à quelques reprises, ce qui a entraîné de nombreuses modifications fastidieuses dans toutes les différentes classes où je l'utilise.

Je commence à penser que ce design n'est pas génial, et ce que je peux faire pour le changer. Ce que j'ai pensé, c'est qu'au lieu de passer dans chaque paramètre individuel, je crée une classe ValidationParameters, et je passe juste cela. De cette façon, quand j'ai besoin d'ajouter un autre paramètre - param4 - je n'ai pas à aller à chacun placer dans le code pour effectuer la modification, et peut simplement changer le champ dans la classe ValidationParameters. Cela ne me semble pas être un problème, car toutes les règles n'utilisent pas chaque paramètre, donc je ne vois aucun problème. Comme, Rule1 ne peut utiliser que param2 et param3, pas param1, par exemple.

public class ValidationParameters
{
    public string Param1 {get;set;}
    public string Param2 {get;set;}
    public int Param3 {get;set;}
    public int Param4 {get;set;}
}

et changez l'interface en

    public interface IRuleEngine
{
    List<ValidationMessage> Validate(ValidationParameters);
}

est-ce que cela ressemble à un bon design ou existe-t-il un meilleur modèle à utiliser?

5
ygetarts

Et maintenant je lis la date du post ....

Votre design a l'air vraiment maladroit à mon avis. Je pense que vous devriez utiliser un cadre existant, beaucoup de travail est fait pour le faire correctement. Pour ma part, je ne veux pas voir un autre moteur de règles écrit, comme on dit en roumain "over the knee" (rapidement, de manière ponctuelle, sans trop d'attention aux détails).

Je viens de fouetter ça en 20-30 minutes. Ce n'est en aucun cas ce niveau de production, mais si vous refusez vraiment vraiment d'utiliser un framework et que vous voulez commencer quelque part, je crois que cette abomination peut vous aider. Il devrait offrir plus de flexibilité.

class Employee
{
    public string Name { get; set; }
    public string Address { get; set; }
    public int BadgeNumber { get; set; }
    public decimal salary { get; set; }
    public int age { get; set; }
}

class ValidationResult
{
    public bool Valid { get; private set;}
    public string Message { get; private set; }

    private ValidationResult(bool success, string message = null)
    {

    }

    public static ValidationResult Success()
    {
        return new ValidationResult(true);
    }

    public static ValidationResult Fail()
    {
        return new ValidationResult(true);
    }

    public ValidationResult WithMessage(string message)
    {
        return new ValidationResult(this.Valid, message);
    }
}

class ValidationContext
{
    //might want to make this "freezable"/"popsicle" and perhaps
    //you might want to validate cross-entity somehow
    //will you always make a new entity containing 2 or 3 sub entities for this case?
    List<Rule> rules = new List<Rule>();

    public ValidationContext(params Rule[] rules)
    {
        this.rules = rules.ToList();
    }

    //how do you know each rule applies to which entity?
    private List<ValidationResult> GetResults()
    {
        var results = new List<ValidationResult>();
        foreach (var rule in rules)
        {
            results.Add(rule.Validate(this));
        }

        return results;
    }

    public void AddRule(Rule r)
    {
        this.rules.Add(r);
    }

}

abstract class Rule
{        
    public abstract ValidationResult Validate(ValidationContext context);

    public static Rule<TEntityType> Create<TEntityType>(TEntityType entity, Func<TEntityType, ValidationResult> rule)
    {
        Rule<TEntityType> newRule = new Rule<TEntityType>();
        newRule.rule = rule;
        newRule.entity = entity;
        return newRule;
    }
}

class Rule<T> : Rule
{
    internal T entity;
    internal Func<T, ValidationResult> rule;

    public override ValidationResult Validate(ValidationContext context)
    {
        return rule(entity);
    }
}

//usage: only rule creation since I need sleep. I have to hold an interview in 4 hours, I hope you are happy :)
class Program
{
    static void Main(string[] args)
    {
        var employee = new Employee { age = 80 };
        var employeeMustBe80 = Rule.Create(employee,
                                           e => e.age > 80 ?
                                           ValidationResult.Success().WithMessage("he should retire") :
                                           ValidationResult.Fail().WithMessage("this guy gets no pension");
    }
}
3