Advertisements
Home > programming, Software, Uncategorized > The Validator Pattern

The Validator Pattern

For my first contribution, I’ve decided to say something about the Validator pattern. Actually, I don’t know whetherthis is the official name for this design pattern, but I haven’t found any corresponding literature.

The first time I got in touch with the Validator pattern was When I worked on a big GWT project. There are a lot of different open implementations of this pattern available. However, I didn’t utilized one of them since they didn’t fit to my requirements. Though, I’ve got some inspiration by their provided API  (e.g. http://gwt-vl.sourceforge.net/?to=clientDoc).

In my project, I had to implement a lot of client-side input validations which were all almost identical such as input must not be empty, input must be a number and so on. Or course, the reactions triggered by proper or inproper input were also not really different: If an the input is not valid, show an error message and higlight the  affected input element. If everything is allright, send a request to the server  side etc.

if(inputName.getText().isEmpty())
{
   focus(inputName);
   showErrorMsg("Input Field Name must not be empty!");
}
else
{
   if(inputPort.getText().isEmpty())
   {
     focus(inputPort);
     showErrorMsg("Input Field Port must not be empty!");
   } 
   else if( ! isNumber(inputPort.getText()))
   {
     focus(inputPort);
     showErrorMsg("Input Field Port must be a number!");
   }
   else 
   {
     configureServer();
   }
}

As you can see, the complexity even for such simple validations can increase very fast. Keep in mind, that this is just the client-side validation which means that we also have to apply at least those funny validations one more time on the server-side. As you can see in the example above, some validations and their consequent actions are quite similiar. One way to reduce the complexity could be to write methods which handle this such as

public boolean check(TextField field)
{
   if(field.getText().isEmpty())
   {
     focus(field);
     showErrorMsg("Input Field " + field.getName() + " must not be empty");
     return false;
   }
   return true;
}

So what do we do, if some fields need a special treatment? Right! We write another method handling them:

public void performSpecialCheck(TextField field)
{
  if(field.getText().isEmpty())
  {
     focus(field);
     showErrorMsg("Input Field " + field.getName() + " must not be empty");
     disableAllOtherFields();
     return false;
  }
  return true;
}

As may have noticed, there will be more than one “special treatment”. Another point is to write such methods with sequences of validations together with their drawbacks mentioned above:

public boolean checkNumberFields()
{
   if(inputPort.getText().isEmpty())
   {
     focus(inputPort);
     showErrorMsg("Input Field Port must not be empty!");
     return false;
   }
   else if( ! isNumber(inputPort.getText()))
   {
     focus(inputPort);
     showErrorMsg("Input Field Port must be a number!");
     return false;
   }
   return true;
}

Now, let’s have a look at the Validator pattern approach:

//-- Validatation preparations

IValidator emptyNameValidator  = new EmptyFieldValidator(inputName);
IValidator emptyPortValidator  = new EmptyFieldValidator(inputPort);
IValidator numberPortValidator = new NumberFieldValidator(inputPort);

//-- Set up the actions which shall be performed, if the validation is not successful
emptyNameValidator.addActionForFailure(new ErrorMsgAction(inputName));
emptyNameValidator.addActionForFailure(new FocusAction(inputName));

emptyPortValidator .addActionForFailure(new FocusAction(inputPort));
emptyPortValidator .addActionForFailure(new ErrorMsgAction(inputPort));
numberPortValidator.addActionForFailure(new FocusAction(inputPort));
numberPortValidator.addActionForFailure(new ErrorMsgAction(inputPort));

/*
 * Chain the set up validators. The validations are executed in the
 * specified order. If one of the validations within the chain fails,
 * the entire validation stops and the corresponding error actions
 * of the affected validation are performed as well as the failure
 * action of the chain itself. Analogously, successful validations
 * are handled.
 */
ValidatorChain entireValidationChain = new ValidatorChain();
entireValidationChain.addValidator(emptyNameValidator);
entireValidationChain.addValidator(emptyPortValidator);
entireValidationChain.addValidator(numberPortValidator);
entireValidationChain.addActionForSuccess(new IValidatorAction()
{
   public void performAction()
   {
      configureServer(); 
   }
});

//-- Prepared Validation in action
boolean success = entireValidationChain.validate();

OK, it’s a matter of taste whether this example is more or less complex than the previous one. I justed wanted to give you an idea how the pattern works basically. In my project, I created the validator instances within the constructors of the holding class using some helper methods or I utilized a customized validator (chain) (but this will be explained later). But, by all means, the entire validation has become more flexible as it was before.  I will explain why, after we had a look on the pattern and its specification:

/**
 * IValidator is responsible for one atomic validation task. The actual validation
 * is performed with the method validate(). If the validation is successful, validate()
 * returns true and all its success actions are performed according to the FIFO principle.
 * Analogously, failing validations are handled.
 */
public interface IValidator
{
  public boolean validate();
  public void addActionForSuccess(IValidatorAction action);
  public void addActionForFailure(IValidatorAction action);
  public void removeActionForSuccess(IValidatorAction action);
  public void removeActionForFailure(IValidatorAction action);
}

/**
 * Chains multiple IValidator instance. Note that IValidatorChain extends
 * the IValidator interface. When validate() is performed, the validate()
 * method of all added validators performed (FIFO). If all validations have
 * been successful, validate() returns true and registered success actions are
 * performed. Otherwise, if one validation is not successful, the remaining
 * validations are not performed, false is returned and the registered failure
 * actions are performed.
 */
public interface IValidatorChain extends IValidator
{ 
  public void addValidator(IValidator validator);
  public void removeValidator(IValidator validator);
}

/**
 * Represents an action which is performed by an IValidator for success
 * or failure.
 */
public interface IValidatorAction
{
   public void performAction();
}

/**
 * Serves for chaining multiple IValidatorActions. This chain
 * is usually used implementing customized actions.
 */
public interface IValidatorActionChain extends IValidatorAction
{
  public void addValidatorAction(IValidatorAction action);
  public void removeValidatorAction(IValidatorAction action);
}

As you can see, the Validator pattern is quite simple but provides high flexibility. Now, let’s have a look at the modified versions of our checker methods:

// reimplementation of method check(TextField field)
public IValidator createBasicValidator(TextField field)
{
   IValidator validator  = new EmptyFieldValidator(field);
   validator.addActionForFailure(new ErrorMsgAction(field));
   validator.addActionForFailure(new FocusAction(field));
   return validator
}

// reimplementation of method checkNumberFields(TextField field)
public IValidator createValidatorForNumberFields(TextField field)
{
  IValidator basicValidator    = createBasicValidator(field);
  IValidator numberValidator = new NumberFieldValidator(field);

  numberValidator.addActionForFailure(new FocusAction(inputName));
  numberValidator.addActionForFailure(new ErrorMsgAction(inputName));

  IValidatorChain chain = new ValidatorChain();
  chain.addValidator(basicValidator);
  chain.addValidator(numberValidator);

   return numberValidator;
}

If we consider createValidatorForNumberFields(), we can see how we can reuse a conditional statement and customize it which wasn’t possible with our ordinary checker methods. However, there is just place for optimization. For example, we can subsume the validator actions:

public IValidatorAction createBasicErrorAction(TextField field)
{
   IActionChain chain = new ActionChain();
   chain.addValidatorAction(new ErrorMsgAction(field));
   chain.addValidatorAction(new FocusAction(field));
   return chain;
}

IValidator validator  = new EmptyFieldValidator(field);
validator.addActionForFailure(createBasicErrorAction(field));

One further example for emphasizing the flexibility is as follows:

public class MyValidator extends AValidator
{
  private final IValidatorChain chain;

  public MyValidator(TextField field)
  {
    IActionChain successActions = new MyStandandardSuccessActions(field);
    IActionChain failureActions = new MyStandandardFailureActions(field);

    IValidator emptyValidator = new EmptyFieldValidator(field);
    emptyValidator.addActionForSuccess(successActions); 
    emptyValidator.addActionForFailure(failureActions);

    IValidator numValidator = new NumberFieldValidator(field);
    numValidator.addActionForSuccess(successActions);
    numValidator.addActionForFailure(failureActions);

   this.chain = new ValidatorChain();
   this.chain.addValidator(emptyValidator);
   this.chain.addValidator(numValidator);

   IValidatorChain someOtherUsefulValidations = new SomeOtherUsefulValidationChain(text);
   this.chain.addValidator(someOtherUsefulValidations);

   this.chain.addActionForSuccess(new IValidatorAction() 
  {
    // do something
  });
 }

  public boolean performAction()
  {
    return this.chain.validate();
  }
}
}

Conclusion
The Validator pattern provides a flexible way for reusing and customizing frequently used validations. However, one should take care of not overdoing  this since the validations could get too nested and intransparent. However, due to the resulting overhead, one should just use them for GUI implementations. If the overhead is not relevant for the project, one could reuse some customized validators at the client as well as the server side which makes sense, if the server has to perform the same validations (and resulting actions) as the client does.

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: