Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the other solutions for the following rule design?

Tags:

c#

rules

I want to make a simple validation system for certain class of objects, basically

public interface IMyClassRule {
    bool IsValid(MyClass obj, Context someAdditionalData)
}

The list of rules is auto-discovered using DI framework and not fixed in advance.

Let's say I have a VeryGeneralDefaultRuleAboutAllObjects : IMyClassRule and SpecificCaseWhenGeneralRuleDoesNotApply : IMyClassRule. How can I handle this solution in a generic way (basically allowing override of any rule by any other rule in certain cases)?

Solutions I considered:

  1. Numeric priority for rules or rule results

    Pro: Simple to understand and implement.
    Contra: I will need to know/guess priority of the original rule. Not obvious which priority is first (1 or 1000)? Needs some "do not care" rule result for situation where the specific case does not apply.

  2. Type based priority (basically .Before<VeryGeneralRule>)

    Pro: Specifically declares what you want to achieve.
    Contra: Needs explicit reference to the original rule. Ordering logic will be complicated. Needs some "do not care" rule result for situation where the specific case does not apply.

Are there any other/better options?

like image 884
Andrey Shchekin Avatar asked Jan 09 '12 22:01

Andrey Shchekin


People also ask

What are the 4 design rules?

The four graphic design principles are contrast, repetition, alignment, and proximity (C.R.A.P.).

What are the 5 rules of design?

There are 5 important principles to take into consideration which are: balance, rhythm and repetition, emphasis, proportion and scale, and last but not least, harmony. Balance.


1 Answers

I think a lot of this will depend on the scope of the project and just how loosely coupled you need to be. I do a lot of work around business rules, and they need to be about as extensible as possible. I would not tie yourself to an ordinal rule system if there is even the slightest volume of rules, or the ordering of them is even remotely complex. I think auto-discovery/wiring of the rules is absolutely the way to go here.

The key to this kind of situation, in my opinion, is that the general case rules are not defined by an absence of logic related to their scope. The general case rules must have scope logic just as specific as the specific case rules. They may be in scope 99 out of 100 times, but they still need to have specific scope logic.

The following is roughly how I'd approach this. I'm not thrilled with WithinScope() being attached to IRule directly, but given that you're considering an ordinal list, I'm assuming the logic is either manageable and relatively static, or that you could inject a delegate for that logic.

Framework Interfaces

public interface IRule<in T>{
    bool IsValid(T obj);
    bool WithinScope();
}

public interface IValidator<in T>{
    bool IsValid(T obj);
}

public interface IRuleFactory<in T>{
    IEnumerable<IRule<T>> BuildRules();
}

Generic Validator and Rule Factory

public class GenericValidator<T> : IValidator<T>
{
    private readonly IEnumerable<IRule<T>> _rules;

    public GenericValidator(IRuleFactory<T> ruleFactory){
        _rules = ruleFactory.BuildRules();
    }

    public bool IsValid(T obj){
        return _rules.All(p => p.IsValid(obj));
    }
}

public class GenericRuleFactory<T> : IRuleFactory<T>
{
    private readonly IEnumerable<IRule<T>> _rules;

    public GenericRuleFactory(IEnumerable<IRule<T>> rules){
        _rules = rules;
    }

    public IEnumerable<IRule<T>> BuildRules(){
        return _rules.Where(x => x.WithinScope());
    }
}

Sample Rules

public class VeryGeneralDefaultRuleAboutAllObjects : IRule<IMyClass>
{
    private readonly Context _context;

    public VeryGeneralDefaultRuleAboutAllObjects(Context context){
        _context = context;    
    }

    public bool IsValid(IMyClass obj){
        return !obj.IsAllJackedUp;
    }

    public bool WithinScope(){
        return !_context.IsSpecialCase;
    }
}

public class SpecificCaseWhenGeneralRuleDoesNotApply : IRule<IMyClass>
{
    private readonly Context _context;

    public VeryGeneralDefaultRuleAboutAllObjects(Context context){
        _context = context;    
    }

    public bool IsValid(IMyClass obj){
        return !obj.IsAllJackedUp && _context.HasMoreCowbell;
    }

    public bool WithinScope(){
        return _context.IsSpecialCase;
    }
}

IoC Wiring (Using StructureMap)

public static class StructureMapBootstrapper
{
    public static void Initialize()
    {
        ObjectFactory.Initialize(x =>
        {
            x.Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.AssembliesFromApplicationBaseDirectory();
                scan.AddAllTypesOf(typeof (IRule<>));
            });

            x.For(typeof(IValidator<>))
                .Use(typeof(GenericValidator<>));

            x.For(typeof(IRuleFactory<>))
                .Use(typeof(GenericRuleFactory<>));
        });
    }
}
like image 98
Chris Hines Avatar answered Sep 29 '22 09:09

Chris Hines