Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For a large validation task is chain of responsibility pattern a good bet?

I need to build a process which will validate a record against ~200 validation rules. A record can be one of ~10 types. There is some segmentation from validation rules to record types but there exists a lot of overlap which prevents me from cleanly binning the validation rules.

During my design I'm considering a chain of responsibility pattern for all of the validation rules. Is this a good idea or is there a better design pattern?

like image 328
yamori Avatar asked May 01 '14 13:05

yamori


People also ask

What is the correct situation for the use of a chain of responsibility pattern?

Where and When Chain of Responsibility pattern is applicable : When you want to decouple a request's sender and receiver. Multiple objects, determined at runtime, are candidates to handle a request. When you don't want to specify handlers explicitly in your code.

Which statement is true about chain of responsibility pattern?

A Chain of Responsibility Pattern says that just "avoid coupling the sender of a request to its receiver by giving multiple objects a chance to handle the request".

What is the characteristics about chain of responsibility in what scenarios can chain of responsibility be used?

As the name suggests, the chain of responsibility pattern creates a chain of receiver objects for a request. This pattern decouples sender and receiver of a request based on type of request. This pattern comes under behavioral patterns. In this pattern, normally each receiver contains reference to another receiver.

What is called as chain of responsibility?

Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.


1 Answers

Validation is frequently a Composite pattern. When you break it down, you want to seperate the what you want to from the how you want to do it, you get:

If foo is valid then do something.

Here we have the abstraction is valid -- Caveat: This code was lifted from currrent, similar examples so you may find missing symbology and such. But this is so you get the picture. In addition, the

Result

Object contains messaging about the failure as well as a simple status (true/false). This allow you the option of just asking "did it pass?" vs. "If it failed, tell me why"

QuickCollection

and

QuickMap

Are convenience classes for taking any class and quickly turning them into those respected types by merely assigning to a delegate. For this example it means your composite validator is already a collection and can be iterated, for example.

You had a secondary problem in your question: "cleanly binding" as in, "Type A" -> rules{a,b,c}" and "Type B" -> rules{c,e,z}"

This is easily managed with a Map. Not entirely a Command pattern but close

Map<Type,Validator> typeValidators = new HashMap<>();

Setup the validator for each type then create a mapping between types. This is really best done as bean config if you're using Java but Definitely use dependency injection

    public interface Validator<T>{
    
    
        public Result validate(T value);
    
    
        public static interface Result {
    
            public static final Result OK = new Result() {
                @Override
                public String getMessage() {
                    return "OK";
                }
    
                @Override
                public String toString() {
                    return "OK";
                }
    
                @Override
                public boolean isOk() {
                    return true;
                }
            };
    
            public boolean isOk();
    
            public String getMessage();
        }
    }

Now some simple implementations to show the point:

public class MinLengthValidator implements Validator<String> {
    
    private final SimpleResult FAILED;
    
    private Integer minLength;
    
    public MinLengthValidator() {
        this(8);
    }
    
    public MinLengthValidator(Integer minLength) {
        this.minLength = minLength;
        FAILED = new SimpleResult("Password must be at least "+minLength+" characters",false);
    }
    
    @Override
    public Result validate(String newPassword) {
        return newPassword.length() >= minLength ? Result.OK : FAILED;
    }
    
    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}

Here is another we will combine with

    public class NotCurrentValidator implements Validator<String> {
    
        @Autowired
        @Qualifier("userPasswordEncoder")
        private PasswordEncoder encoder;
    
        private static final SimpleResult FAILED = new SimpleResult("Password cannot be your current password",false);
    
        @Override
        public Result validate(String newPassword) {
            boolean passed = !encoder.matches(newPassword,user.getPassword());
            return (passed ? Result.OK : FAILED);
        }
    
        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }
    
    }

Now here is a composite:

public class CompositePasswordRule extends QuickCollection<Validator> implements Validator<String> {


public CompositeValidator(Collection<Validator> rules) {
    super.delegate = rules;
}

public CompositeValidator(Validator<?>... rules) {
    super.delegate = Arrays.asList(rules);
}



@Override
public CompositeResult validate(String newPassword) {
    CompositeResult result = new CompositeResult(super.delegate.size());
    for(Validator rule : super.delegate){
        Result temp = rule.validate(newPassword);
        if(!temp.isOk())
            result.put(rule,temp);
    }

    return result;
}


    public static class CompositeResult extends QuickMap<Validator,Result> implements Result {
        private Integer appliedCount;

        private CompositeResult(Integer appliedCount) {
            super.delegate = VdcCollections.delimitedMap(new HashMap<PasswordRule, Result>(), "-->",", ");
            this.appliedCount = appliedCount;
        }

        @Override
        public String getMessage() {
            return super.delegate.toString();
        }

        @Override
        public String toString() {
            return super.delegate.toString();
        }

        @Override
        public boolean isOk() {
            boolean isOk = true;
            for (Result r : delegate.values()) {
                isOk = r.isOk();
                if(!isOk)
                    break;
            }
            return isOk;
        }
        public Integer failCount() {
            return this.size();
        }

        public Integer passCount() {
            return appliedCount - this.size();
        }
    }
}

and now a snippet of use:

private Validator<String> pwRule = new CompositeValidator<String>(new MinLengthValidator(),new NotCurrentValidator());

Validator.Result result = pwRule.validate(newPassword);
if(!result.isOk())
    throw new PasswordConstraintException("%s", result.getMessage());

user.obsoleteCurrentPassword();
user.setPassword(passwordEncoder.encode(newPassword));
user.setPwExpDate(DateTime.now().plusDays(passwordDaysToLive).toDate());
userDao.updateUser(user);
like image 87
Christian Bongiorno Avatar answered Oct 13 '22 23:10

Christian Bongiorno