Un design migliore per l'implementazione di un motore di regole

5

Ho implementato un motore di regole come questo:

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

Ora, questo funziona bene, ma la manutenzione è stata noiosa. Uso il motore delle regole in molti punti del mio codice. Ho anche dovuto cambiare i parametri necessari in alcune volte, il che ha portato a molte noiose modifiche in tutte le diverse classi in cui lo uso.

Sto iniziando a pensare che questo design non sia eccezionale, e cosa posso fare per cambiarlo. Quello che stavo pensando, invece di passare in ogni singolo parametro, creo una classe ValidationParameters, e la faccio passare. In questo modo, quando ho bisogno di aggiungere un altro parametro - param4 - non devo andare in ogni inserire il codice per apportare la modifica e modificare semplicemente il campo nella classe ValidationParameters. Questo non è un problema, poiché non tutte le regole utilizzano ciascun parametro, quindi non vedo alcun problema. Come, la Regola 1 può usare solo param2 e param3, non param1, per esempio.

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

e cambia l'interfaccia in

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

questo sembra un buon design, o c'è un modello migliore da usare?

    
posta ygetarts 12.10.2017 - 20:28
fonte

1 risposta

2

E ora leggo la data del post ....

Il tuo design sembra davvero goffo secondo me. Penso che dovresti usare un framework esistente, molto lavoro va nel farlo correttamente. Io per primo non voglio vedere un altro motore di regole scritto, come diciamo in rumeno "sopra il ginocchio" (rapidamente, in maniera ad-hoc, senza molta attenzione ai dettagli).

L'ho appena estratto in 20-30 minuti. Non è affatto questo grado di produzione, ma se davvero ti rifiuti davvero di usare un quadro e vuoi iniziare da qualche parte, credo che questo abominio possa aiutarti. Dovrebbe fornire maggiore flessibilità.

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");
    }
}
    
risposta data 06.03.2018 - 01:58
fonte

Leggi altre domande sui tag