Avoiding Many If Blocks For Validation Checking
Join the DZone community and get the full member experience.
Join For FreeThere are cases that we want to validate input data before we send them to business logic layer for processing, computations etc. This validation, in most cases, is done in isolation or it might include some cross-checking with external data or other inputs. Take a look at the following example that validates user input for registration data.
public void register(String email, String name, int age) {
String EMAIL_PATTERN =
"^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
+ "+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
Pattern pattern = Pattern.compile(EMAIL_PATTERN);
List<String> forbiddenDomains = Arrays.asList("domain1", "domain2");
if ( email == null || email.trim().equals("")){
throw new IllegalArgumentException("Email should not be empty!");
}
if ( !pattern.matcher(email).matches()) {
throw new IllegalArgumentException("Email is not a valid email!");
}
if ( forbiddenDomains.contains(email)){
throw new IllegalArgumentException("Email belongs to a forbidden email");
}
if ( name == null || name.trim().equals("")){
throw new IllegalArgumentException("Name should not be empty!");
}
if ( !name.matches("[a-zA-Z]+")){
throw new IllegalArgumentException("Name should contain only characters");
}
if ( age <= 18){
throw new IllegalArgumentException("Age should be greater than 18");
}
// More code to do the actual registration
}
First create an interface with one method. In Java 8 terms, it’s called a functional interface, like the following.
public interface RegistrationRule{
void validate();
}
Now it’s time to transform each validation check to a registration rule. But before we do that we need to address a small issue. Our interface implementation should be able to handle registration data but as you see we have different types of data. So what we need here is to encapsulate registration data in a single object like this :
public class RegistrationData{
private String name;
private String email;
private int age;
// Setters - Getters to follow
}
Now we can improve our functional interface:
public interface RegistrationRule{
void validate(RegistrationData regData);
}
and start writing our rule set. For instance let’s try to implement the email validation.
public class EmailValidatationRule implements RegistrationRule{
private static final String EMAIL_PATTERN =
"^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
+ "+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
private final Pattern pattern = Pattern.compile(EMAIL_PATTERN);
@Override
public void validate(RegistrationData regData) {
if ( !pattern.matcher(regData.email).matches()) {
throw new IllegalArgumentException("Email is not a valid email!");
}
}
It’s clear that we have isolated in the above class the email validation. We can do the same for all rules of our initial implementation. Now we can re-write our register method to use the validation rules.
List<RegistrationRule> rules = new ArrayList<>();
rules.add(new EmailValidatationRule());
rules.add(new EmailEmptinessRule());
rules.add(new ForbiddenEmailDomainsRule());
rules.add(new NameEmptinessRule());
rules.add(new AlphabeticNameRule());
for ( RegistrationRule rule : rules){
rule.validate(regData);
}
To make it even better we can create a Rules class using the Factory pattern and a static method get() that will return the list of rules. And our final implementation will look like this
for ( RegistrationRule rule : Rules.get()){
rule.validate(regData);
}
Comparing the initial version of our register method to the final one leaves room for doubts. Our new version is more compact, more readable and of course more testable. The actual checks have been moved to separate classes (which are easy to test also) and all methods do only one thing ( try to always keep that in mind ).
If you found this post useful please don’t forget to share it and if you have some spare time take a look at my upcoming book about the “Art of Software Gardening”
Published at DZone with permission of Patroklos Papapetrou, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments