Verify input with Model Objects

Validation of user input is important for almost all applications. There seems to be two places where you may implement such validation logic

Implementing validation in the Model Object has some strong advantages:

Example

Here's a Model Object class named Movie. It performs all its validation in its constructors. If a problem occurs, a checked exception named InvalidUserInput is thrown.


/**
 Data-centric class encapsulating all fields related to movies (a 'model object'). 
 
 <P>This class exists  in order to encapsulate, validate, and sort movie information.
  This class is used both to validate user input, and act as a 'transfer object' when 
  interacting with the database.
*/
public final class Movie implements Comparable<Movie>{

  /**
   Constructor taking regular Java objects natural to the domain.
   
   @param aId optional, the database identifier for the movie. This 
   item is optional since, for 'add' operations, it has yet to be 
   assigned by the database.
   @param aTitle has content, name of the movie
   @param aDateViewed optional, date the movie was screened by the user
   @param aRating optional, in range 0.0 to 10.0
   @param aComment optional, any comment on the movie
  */
  Movie(
    String aId, String aTitle, Date aDateViewed, BigDecimal aRating, String aComment
  ) throws InvalidInputException {
    fId = aId;
    fTitle = aTitle;
    fDateViewed = aDateViewed;
    fRating = aRating;
    fComment = aComment;
    validateState();
  }
  
  /**
   Constructor which takes all parameters as <em>text</em>.
   
   <P>Raw user input is usually in the form of <em>text</em>.
   This constructor <em>first</em> parses such text into the required 'base objects' - 
   {@link Date}, {@link BigDecimal} and so on. If those parse operations <em>fail</em>,
   then the user is shown an error message. If N such errors are present in user input, 
   then  N <em>separate</em> message will be presented for each failure, one by one.
   
   <P>If all such parse operations <em>succeed</em>, then the "regular" constructor 
   {@link #Movie(String, String, Date, BigDecimal, String)}
   will then be called. It's important to note that this call to the second constructor 
   can in turn result in <em>another</em> error message being shown to the 
   user (just one this time). 
 */
  Movie(
    String aId, String aTitle, String aDateViewed, String aRating, String aComment
  ) throws InvalidInputException {
      this(
        aId, aTitle, Util.parseDate(aDateViewed, "Date Viewed"), 
        Util.parseBigDecimal(aRating, "Rating"), aComment
      );
  }
  
  String getId(){ return fId; }
  String getTitle(){ return fTitle; }
  Date getDateViewed(){ return fDateViewed; }
  BigDecimal getRating(){ return fRating; }
  String getComment(){ return fComment; }

  //elided...
  
  // PRIVATE
  private String fId;
  private final String fTitle;
  private final Date fDateViewed;
  private final BigDecimal fRating;
  private final String fComment;
  private static final BigDecimal TEN = new BigDecimal("10.0");
  private static final int EQUAL = 0;
  private static final int DESCENDING = -1;

  //elided...
  
  private void validateState() throws InvalidInputException {
    InvalidInputException ex = new InvalidInputException();
    
    if(! Util.textHasContent(fTitle)) {
      ex.add("Title must have content");
    }
    if (fRating != null){
      if (fRating.compareTo(BigDecimal.ZERO) < 0) {
        ex.add("Rating cannot be less than 0.");
      }
      if (fRating.compareTo(TEN) > 0) {
        ex.add("Rating cannot be greater than 10.");
      }
    }
    if (ex.hasErrors()) {
      throw ex;
    }
  }
} 

The user edits Movie data using a dialog (not listed here). When the user hits a button, execution passes to the following MovieController class. The actionPerformed method first attempts to build a Movie object from user input. If a problem is detected, then an error message is displayed to the user.

package hirondelle.movies.edit;

import hirondelle.movies.exception.InvalidInputException;
import hirondelle.movies.main.MainWindow;
import hirondelle.movies.util.Edit;
import hirondelle.movies.util.Util;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Logger;

import javax.swing.JOptionPane;

/**
 Add a new {@link Movie} to the database, or change an existing one. 
 
 <P>It's important to note that this class uses most of the other classes in 
 this feature to get its job done (it doesn't use the <tt>Action</tt> classes):
 <ul>
   <li>it gets user input from the view - {@link MovieView}
   <li>it validates user input using the model - {@link Movie}
   <li>it persists the data using the Data Access Object  - {@link MovieDAO}
 </ul>
*/
final class MovieController implements ActionListener {
  
  /**
   Constructor.
   @param aView user interface
   @param aEdit identifies what type of edit - add or change 
  */
  MovieController(MovieView aView, Edit aEdit){
    fView = aView;
    fEdit = aEdit;
  }
  
  /**
   Attempt to add a new {@link Movie}, or edit an existing one.
   
   <P>If the input is invalid, then inform the user of the problem(s).
   If the input is valid, then add or change the <tt>Movie</tt>, close the dialog, 
   and update the main window's display.  
  */
  @Override public void actionPerformed(ActionEvent aEvent){
    fLogger.fine("Editing movie " + fView.getTitle());
    try {
      createValidMovieFromUserInput();
    }
    catch(InvalidInputException ex){
      informUserOfProblems(ex);
    }
    if ( isUserInputValid() ){
      if( Edit.ADD == fEdit ) {
        fLogger.fine("Add operation.");
        fDAO.add(fMovie);
      }
      else if (Edit.CHANGE == fEdit) {
        fLogger.fine("Change operation.");
        fDAO.change(fMovie);
      }
      else {
        throw new AssertionError();
      }
      fView.closeDialog();
      MainWindow.getInstance().refreshView();
    }
  }

  // PRIVATE 
  private final MovieView fView;
  private Movie fMovie;
  private Edit fEdit;
  private MovieDAO fDAO = new MovieDAO();
  private static final Logger fLogger = Util.getLogger(MovieController.class);
  
  private void createValidMovieFromUserInput() throws InvalidInputException {
    fMovie = new Movie(
      fView.getId(), fView.getTitle(), fView.getDateViewed(), 
      fView.getRating(), fView.getComment()
    );
  }

  private boolean isUserInputValid(){
    return fMovie != null;
  }
  
  private void informUserOfProblems(InvalidInputException aException) {
    Object[] messages = aException.getErrorMessages().toArray();
    JOptionPane.showMessageDialog(
      fView.getDialog(), messages, 
      "Movie cannot be saved", JOptionPane.ERROR_MESSAGE
    );
  }
} 



See Also :
Immutable objects
Use a testing framework (JUnit)
Verify input with regular expressions
Input dialogs
Validation belongs in a Model Object
Would you use this technique?
Yes   No   Undecided   
© 2014 Hirondelle Systems | Source Code | Contact | License | RSS
Individual code snippets can be used under this BSD license - Last updated on September 21, 2013.
Over 2,000,000 unique IPs last year - Built with WEB4J.
- In Memoriam : Bill Dirani -