Design pattern for validating input?
What is the best way to validate input? For argument's sake, if the input is invalid, the user would like a message explaini开发者_如何学Pythonng why.
Here is what I can come up with.
Validator
method: Pass input to the validator, which returnstrue
if the input is valid. Otherwise, the validator either returnsfalse
(or an error code) and lets the caller handle the invalid input. Or the validator takes the responsibility of taking action itself. Or the validator calls a callback method. Drawbacks: the steps taken to validate might be repeated when the actual method is called.Pass the input directly to the method, without validation. Let the method handle invalid messages itself. It can either send the error message to the user directly or use a callback method. After sending the message, the method must return or throw an exception to stop processing the invalid input. The calling class will continue to the next line of input. Drawbacks: this method now has a side effect of sending an error message.
What is the appropriate strategy here? Note that I do not believe throwing exceptions is appropriate because handling invalid input is a core function of the application, at least in my case.
Have a look at Spring's data binding and validation framework. Very nice indeed, and designed to be usable either on its own or as part of a UI.
A nice solution for objects validation without the burden of creating many classes that implement a "validator" interface or using anonymous inner classes that are not reusable, is to use the constant functionality of an enum:
In example:
public enum ObjectValidation {
BAD_DATE_FORMAT(ErrorCode.BAD_DATE_FORMAT) {
@Override
boolean validate(String value, List<String> acceptableValues) {
boolean isValid = false;
// validate the date
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
try {
sdf.parse(value);
isValid = true;
} catch (ParseException ex) {
}
/*
* We could use acceptableValues to validate the value against a
* list of acceptable values
*/
return isValid;
}
};
private ErrorCode errorCode;
private ObjectValidation(ErrorCode ErrorCode) {
this.errorCode = ErrorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
abstract boolean validate(String value, List<String> acceptableValues);
}
And the service method that leverage the constant validation functionality provided by the enum and can be used for multiple validations of the same field:
public static List<ErrorCode> validate(String value, Set<ObjectValidation> objectValidators) throws Exception{
List<ErrorCode> errorCodeList = new ArrayList<ErrorCode>();
for (ObjectValidation objectValidation : objectValidators) {
if(!objectValidation.validate(value, Collections.emptyList())){
errorCodeList.add(objectValidation.getErrorCode());
}
}
return errorCodeList;
}
Maybe look at the Command Pattern and put the validation logic in the invoker class.
You shouldn't pass the input directly to the method (I guess that by "the method" you mean some business logic) to be honest as it couples the view and the model. What you should do is to make either a controller with a validation method or a separate validation class which would take the input and validate it using some "external" methods. Then depending on the result the controller/validator can either return a validation error or forward the input wherever you want. The biggest benefit of this as I said earlier is the decoupling of the model and the view. The model shouldn't really know anything about the view (what if you want to change the view? you'll have to rewrite your model!). I'm not really sure why would you want to duplicate the validation code in the business model? Validation and navigation should be what a controller does. At least that's how the MVC pattern does things.
Also throwing exceptions isn't that bad but they should be thrown in the model and caught in the controller. For example you have a system where you want users to have unique logins, the user inputs a login that is already in the DB, the controller calls a validation method which tries to (using the model) insert it in the DB. If it all goes well then it is inserted and the controller can return a "you inserted a value successfuly" message. But if the model throws an exception (like Unique Constraint Violation error or something) then the controller should simply catch it and return "this login already exists in the DB". This way the model knows nothing about the view and is reusable and you have no code duplication.
now I'm using Hibernate validator framework, very simple and usefull:
I annotate classes with what I need and then use it:
My entity class:
public class Content {
@NotNull
private Heading heading;
@NotNull
@Length(max = 8)
private String contentType;
// Everything else
}
My Validator Component:
@Component
public class Validator implements IValidator {
protected static Log log = LogFactory.getLog(Validator.class);
javax.validation.Validator validator;
@Autowired
WebApplicationExceptionBuilder webApplicationExceptionBuilder;
public void validate (Object entity) throws WebApplicationException {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<Object>> violations = validator.validate(entity);
if (!violations.isEmpty()) {
webApplicationExceptionBuilder
.raise("some fields are missing or incorrect",
violations);
}
}
}
Where to use it:
public class Foo{
@Autowired
private IValidator validator;
@Autowired
private IContentService contentService;
public void bar(Content c) throws Exception{
validator.validate(c);
contentService.persist(content);
}
}
public interface Validator {
/**
* Validates a JComponent</code>.
* @param component
* The component to validate.
* @param message
* The message to display if the validation fails.
* @return
* Returns <code>false</code> if the validation fails, and
* <code>true</code> otherwise.
*/
boolean validate(JComponent component, String message);
}
You could have an AbstractValidator(Extends javax.swing.InputVerifier) class that does handles message display next to the JComponent with an invalid entry. Have a look at this example
精彩评论