如何以一种通用的类型安全的方式验证类层次结构?

3
我遇到了一项看似非常简单的任务,但却陷入困境。我有一个类层次结构,其中每个类都可以定义自己的验证规则。定义验证规则应尽可能简单。以下是几乎需要的内容:
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;
    }
}

这里还需要一件事情-在添加验证规则时需要类型安全。否则,每个子类都必须进行看起来很奇怪的强制转换。理想情况下,Func<T, bool> 可以工作,但是这里有一堆问题:我们不能从任何 IValidatable<HierarchyBase> 类继承我们的 HierarchyBase,因为继承层次可以是 N 级深(是的,我也感受到了那种味道);在 rules 中存储任何具体的 Func<HierarchyBaseInheritor, bool> 并遍历它们。

你会如何在这里引入类型安全?


我不确定我理解问题,引入泛型并在HierarchyBase<T>中使用Func<T, bool>有什么问题吗? - TheCodeKing
2个回答

3
正确的做法是使层次结构中的每个类都负责自己的验证:
层次基类:
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);
    }
}

使用方法:

var result = new HierarchyBaseInheritorB();
result.A = 12;
result.B = 42;
result.C = 0;
bool valid = result.Validate(); // == true

1
在这里使用lambda表达式没有任何好处;AddRule方法是受保护的,因此规则集无法从外部注入。 - KeithS
这些规则不是类不变量,验证规则可能非常复杂。Lambda仅用于简化,并可替换为例如ValidationRule对象。我们将通过将规则分离来避免在这些类中混合不同的职责。也就是说,我们不会“使层次结构中的每个类负责自己的验证”,而是“使层次结构中的每个类负责定义其验证规则”。 - Vitaly Stakhov
@dbt:在经典面向对象编程中,要么是使用继承,要么是使用带有 e.Cancel 的事件。我只是有一种感觉,OP 正试图获得更多“声明式”解决方案,而不是“功能性”的解决方案。顺便说一句,我喜欢你基于 <T> 的答案 :) - DK.
1
KeithS:很容易想象验证规则从数据库或XML文件中加载。 - Gabe

3

注意: 以下解决方案是我和Eric Lippert之间的内部玩笑。它可以工作,但可能不建议使用。

这个想法是定义一个泛型类型参数,它引用“当前”类型(就像this引用“当前”对象一样)。

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

使用方法:

var result = new HierarchyBaseInheritorB();
result.A = 12;
result.B = 42;
result.C = 0;
bool valid = result.Validate(); // == true

我一直在脑海中想到这个解决方案,但是甚至不想注意它 :) - Vitaly Stakhov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接