it-swarm.com.de

Ein besseres Design für die Implementierung einer Regelengine

Ich habe eine Regel-Engine wie diese implementiert:

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

Nun, das funktioniert gut, aber die Wartung war mühsam. Ich verwende die Regelengine an vielen Stellen in meinem Code. Ich musste auch die Parameter ändern, die es einige Male benötigt, was zu vielen mühsamen Änderungen in all den verschiedenen Klassen geführt hat, in denen ich es verwende.

Ich fange an zu denken, dass dieses Design nicht so toll ist und was ich tun kann, um es zu ändern. Ich habe gedacht, anstatt jeden einzelnen Parameter zu übergeben, erstelle ich eine ValidationParameters-Klasse und übergebe sie einfach. Auf diese Weise muss ich nicht zu jedem Parameter wechseln, wenn ich einen weiteren Parameter hinzufügen muss - param4 Platzieren Sie den Code, um die Änderung vorzunehmen, und ändern Sie einfach das Feld in der ValidationParameters-Klasse. Dies sehe ich nicht als Problem an, da nicht jede Regel jeden Parameter verwendet, sodass ich keine Probleme sehe. Ebenso kann Regel1 nur param2 und param3 verwenden, nicht beispielsweise param1.

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

und ändern Sie die Schnittstelle zu

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

sieht das nach einem guten Design aus oder gibt es ein besseres Muster?

5
ygetarts

Und jetzt habe ich das Datum der Post gelesen ...

Ihr Design sieht meiner Meinung nach wirklich klobig aus. Ich denke, Sie sollten ein vorhandenes Framework verwenden, es wird viel Arbeit investiert, um dies richtig zu machen. Ich jedenfalls möchte nicht, dass eine andere Regel-Engine geschrieben wird, wie wir auf Rumänisch "über das Knie" sagen (schnell, ad-hoc, ohne viel Liebe zum Detail).

Ich habe das gerade in 20-30 Minuten ausgepeitscht. Auf keinen Fall ist diese Produktionsklasse, aber wenn Sie sich wirklich wirklich wirklich weigern, ein Framework zu verwenden, und irgendwo anfangen möchten, glaube ich, dass dieser Gräuel Ihnen helfen kann. Es sollte mehr Flexibilität bieten.

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