-->

Making Validation Generic

2019-06-10 03:10发布

问题:

I have the following C# code. Here the validations are kept outside the class to satisfy Open – Closed Principle. This is working fine. But the challenge is – the validations are not generic. It is specific to employee class (E.g DateOfBirthRuleForEmployee). How do I make the validations generic for all objects (DateOfBirthRuleForAnyObject).

Note: Make Generic <==> Make Type-Independent

Note: I have NameLengthRuleForEmployee validation also. New validation may come in future.

EDIT

Generic Method Example: Using “OfType” in LINQ

CODE

    class Program
    {
    static void Main(string[] args)
    {
        Employee employee = new Employee();
        employee.DateOfBirth = DateTime.Now;
        employee.Name = "Lijo";
        DateOfBirthRuleForEmployee dobRule = new
        DateOfBirthRuleForEmployee();
        NameLengthRuleForEmployee nameRule = new
        NameLengthRuleForEmployee();
        EmployeeManager employeeManager = new EmployeeManager();
        employeeManager.AddRules(dobRule);
        employeeManager.AddRules(nameRule);
        bool result = employeeManager.validateEntity(employee);
        Console.WriteLine(result);
        Console.ReadLine();
    }
}
public interface IEntity
{
}
public interface IRule<TEntity>
{
    bool IsValid(TEntity entity);
}
public class DateOfBirthRuleForEmployee : IRule<Employee>
{
    public bool IsValid(Employee entity)
    {
        return (entity.DateOfBirth.Year <= 1975);
    }
}
public class NameLengthRuleForEmployee : IRule<Employee>
{
    public bool IsValid(Employee employee)
    {
        return (employee.Name.Length < 5);
    }
}
public class Employee : IEntity
{
    private DateTime dateOfBirth;
    private string name;
    public DateTime DateOfBirth
    {
        get
        {
            return dateOfBirth;
        }
        set
        {
            dateOfBirth = value;
        }
    }
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}
public class EmployeeManager
{
    RulesEngine<Employee> engine = new RulesEngine<Employee>();
    public void AddRules(IRule<Employee> rule)
    {
        engine.AddRules(rule);
        //engine.AddRules(new NameLengthRuleForEmployee());
    }
    public bool validateEntity(Employee employee)
    {
        List<IRule<Employee>> rulesList = engine.GetRulesList();
        //No need for type checking. Overcame Invariance problem
        bool status = true;
        foreach (IRule<Employee> theRule in rulesList)
        {
            if (!theRule.IsValid(employee))
            {
                status = false;
                break;
            }
        }
        return status;
    }
}
public class RulesEngine<TEntity> where TEntity : IEntity
{
    private List<IRule<TEntity>> ruleList = new
    List<IRule<TEntity>>();
    public void AddRules(IRule<TEntity> rule)
    {
        //invariance is the key term
        ruleList.Add(rule);
    }
    public List<IRule<TEntity>> GetRulesList()
    {
        return ruleList;
    }
}

回答1:

The challange is for your rules to know which property of what type to validate. You can either provide this by implementing an interface that provides just that as suggested by SLaks or by quessing it dynamically or by providing a concrete rule class with a bit more information on how to access the given property, e.g.:

class NameRule<T> : IRule<T>  
{
    private Func<T, string> _nameAccessor;

    public NameRule(Func<T, string> nameAccessor)
    {
        _nameAccessor = nameAccessor;
    }

    public bool IsValid(T instance)
    {
        return _nameAccessor(instance).Length > 10;
    }
}

this ofcourse can be used in the following way:

NameRule<Employee> employeeNameRule = new NameRule<Employee>(x => x.name);
employeeManager.addRule(employeeNameRule);