Um design melhor para uma implementação de mecanismo de regras

5

Eu implementei um mecanismo de regras como este:

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

Agora, isso funciona bem, mas a manutenção foi tediosa. Eu uso o mecanismo de regras em muitos lugares no meu código. Eu também tive que alterar os parâmetros necessários algumas vezes, o que levou a muitas modificações tediosas em todas as diferentes classes em que eu as utilizo.

Estou começando a achar que esse design não é ótimo, e o que posso fazer para mudá-lo. O que eu tenho pensado é, em vez de passar em cada parâmetro individual, eu crio uma classe ValidationParameters, e apenas passo isso. Assim, quando eu preciso adicionar outro parâmetro - param4 - eu não tenho que ir para cada coloque no código para fazer a modificação, e pode apenas mudar o campo na classe ValidationParameters. Isso não é um problema, pois nem todas as regras usam cada parâmetro, por isso não vejo problemas. Como, a regra 1 só pode usar param2 e param3, não param1, por exemplo.

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

e mude a interface para

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

isso parece um bom design ou há um padrão melhor para usar?

    
por ygetarts 12.10.2017 / 20:28
fonte

1 resposta

2

E agora eu li a data do post ...

Seu design parece realmente desajeitado na minha opinião. Eu acho que você deveria usar um framework existente, muito trabalho é feito corretamente. Eu, pelo menos, não quero ver outro mecanismo de regras escrito, como dizemos em romeno "over the knee" (rapidamente, de maneira ad-hoc, sem muita atenção aos detalhes).

Acabei de tirar isso em 20 a 30 minutos. De modo algum é este grau de produção, mas se você realmente realmente se recusar a usar um quadro e quiser começar em algum lugar, acredito que essa abominação pode ajudá-lo. Deve fornecer mais flexibilidade.

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