views:

78

answers:

2

Hey guys,

I'm stuck with what seemed to be a very simple task at the very beginning. I have a class hierarchy each class in which can define its own validation rules. Defining validation rules should be as simple as possible. Here is what is almost what is needed:

class HierarchyBase
{

    private List<Func<object, bool>> rules = new List<Func<object, bool>>();
    public int fieldA = 0;

    public HierarchyBase()
    {
        AddRule(x => ((HierarchyBase)x).fieldA % 2 == 0);
    }

    protected virtual void Operation()
    {
        fieldA++;
    }

    protected void AddRule(Func<object, bool> validCriterion)
    {
        rules.Add(validCriterion);
    }

    public void PerformOperation()
    {
        Operation();
        Validate();
    }

    protected virtual void Operation()
    {
        fieldA++;
    }

    private void Validate()
    {
        IsValid = rules.All(x => x(this));
    }

    public bool IsValid
    {
        get;
        private set;
    }
}

There is one more thing that is needed - type safety when adding validation rules. Otherwise each sub class will have to do those casts that just look awkward. Ideally Func<T, bool> would work, but there is a whole bunch of issues with that: we cannot inherit our HierarchyBase from any kind of IValidatable<HierarchyBase> as the inheritance hierarchy can be N levels deep (yeah, I feel the smell as well); storing any concrete Func<HierarchyBaseInheritor, bool> in rules and traversing them.

How would you introduce type-safety here?

+3  A: 

The right approach is to make each class in the hierarchy responsible for validating itself:

HierarchyBase:

class HierarchyBase
{
    public int A { get; set; }

    public bool Validate()
    {
        return this.OnValidate();
    }

    protected virtual bool OnValidate()
    {
        return (this.A % 2 == 0);
    }
}

HierarchyBaseInheritorA:

class HierarchyBaseInheritorA : HierarchyBase
{
    public int B { get; set; }

    protected override bool OnValidate()
    {
        return base.OnValidate() &&
               (this.A > 10) &&
               (this.B != 0);
    }
}

HierarchyBaseInheritorB:

class HierarchyBaseInheritorB : HierarchyBaseInheritorA
{
    public int C { get; set; }

    protected override bool OnValidate()
    {
        return base.OnValidate() && 
               (this.A < 20) &&
               (this.B > 0) &&
               (this.C == 0);
    }
}

Usage:

var result = new HierarchyBaseInheritorB();
result.A = 12;
result.B = 42;
result.C = 0;
bool valid = result.Validate(); // == true
dtb
+1. The use of lambdas doesn't gain anything here; the AddRule method is protected, so rulesets can't be injected externally.
KeithS
These rules are not class invariants and validation rules may be pretty complex. Lambdas are used just for simplicity and can be substituted with, for example, ValidationRule object. We'll not be mixing different responsibilities withing these classes by separating out the rules.That is instead of 'making each class in the hierarchy responsible for validating itself' we are 'making each class in the hierarchy responsible for defining its validation rules'
Vitaly
@dbt: In classic OOP it's either this inheritance or events with e.Cancel. I just got a feeling that OP was trying to get more "declarative" solution as opposed to "functional". Ah BTW, loved your <T>-based answer :)
DK
KeithS: It's not hard to imagine the validation rules being loaded from a database or XML file.
Gabe
+2  A: 

Note: The following solution is an in-joke between me and Eric Lippert. It works, but is probably not to be recommended.

The idea is to define a generic type parameter that refers to the "current" type (like this refers to the "current" object).

HierarchyBase:

class HierarchyBase<T>
    where T : HierarchyBase<T>
{
    protected readonly List<Func<T, bool>> validators;

    public HierarchyBase()
    {
        validators = new List<Func<T, bool>>();
        validators.Add(x => x.A % 2 == 0);
    }

    public int A { get; set; }

    public bool Validate()
    {
        return validators.All(validator => validator((T)this));
    }
}

HierarchyBaseInheritorA:

class HierarchyBaseInheritorA<T> : HierarchyBase<T>
    where T : HierarchyBaseInheritorA<T>
{
    public HierarchyBaseInheritorA()
    {
        validators.Add(x => x.A > 10);
        validators.Add(x => x.B != 0);
    }

    public int B { get; set; }
}

HierarchyBaseInheritorB:

class HierarchyBaseInheritorB : HierarchyBaseInheritorA<HierarchyBaseInheritorB>
{
    public HierarchyBaseInheritorB()
    {
        validators.Add(x => x.A < 20);
        validators.Add(x => x.B > 0);
        validators.Add(x => x.C == 0);
    }

    public int C { get; set; }
}

Usage:

var result = new HierarchyBaseInheritorB();
result.A = 12;
result.B = 42;
result.C = 0;
bool valid = result.Validate(); // == true
dtb
I had this solution on the back of my head but didn't even want to notice that :)
Vitaly