 |
Class Invariant
A class typically has conditions on the values taken by its fields. The class invariant is simply the sum total of these conditions. If the state of an object satisifes the class invariant, then the object is in a valid state; otherwise, it is not in a valid state.
Example
Board is taken from a web application which implements multiple message boards. It encapsulates the config information related to a single board. It is an ImmutableObject. Its state is established in its constructor, and then never changes. The class invariant is enforced by the call to validateState at the end of the constructor, which in turn calls various methods which validate each field. (In this particular case, a checked exception is thrown if the class invariant is not satisfied by the arguments passed to the constructor. However, other policies are possible.)
Credit for sources below: The example MessageBoard application found at: javapractices.com
package hirondelle.messageboard.model;
import java.util.*;
import java.util.regex.*;
import hirondelle.messageboard.util.Util;
import hirondelle.messageboard.util.Consts;
import hirondelle.messageboard.util.Args;
import hirondelle.messageboard.util.HashCodeUtil;
import hirondelle.messageboard.util.EqualsUtil;
/**
* Value Object (VO) class for items related to the configuration of a
* message board.
*
* @used.By
* {@link hirondelle.messageboard.ui.RequestParser},
* {@link hirondelle.messageboard.ui.ShowBoard},
* {@link hirondelle.messageboard.ui.EditBoard},
* {@link hirondelle.messageboard.data.BoardDAO}
* @is.Immutable
* @author <a href="http://www.javapractices.com/">javapractices.com</a>
*/
public final class Board {
/**
* @param aID uniquely names a message board, contains only letters, and
* has length in range 3..32.
* @param aTitle begins with a non-whitespace character, and has length in
* range 1..50.
* @param aLanguage any member the <code>Language</code> enumeration.
* @param aTimeZone is a time zone identifier from {@link java.util.TimeZone}, which
* is also configured as an acceptable time zone in web.xml.
*/
public Board(String aID, String aTitle, Language aLanguage, String aTimeZone)
throws VOException {
fID = aID;
fTitle = aTitle;
fLanguage = aLanguage;
fTimeZone = aTimeZone;
validateState();
}
public String getID() {
return fID;
}
public String getTitle(){
return fTitle;
}
public Language getLanguage(){
return fLanguage;
}
public String getTimeZone(){
return fTimeZone;
}
public Locale getLocale(){
return Util.getLocale( getLanguage() );
}
/**
* Intended for debugging purposes only.
*/
public String toString() {
StringBuffer result = new StringBuffer();
result.append( this.getClass().getName() );
result.append(" {");
result.append(Consts.NEW_LINE);
result.append(" fID = ").append(fID).append(Consts.NEW_LINE);
result.append(" fTitle = ").append(fTitle).append(Consts.NEW_LINE);
result.append(" fLanguage = ").append(fLanguage).append(Consts.NEW_LINE);
result.append(" fTimeZone = ").append(fTimeZone).append(Consts.NEW_LINE);
result.append("}");
result.append(Consts.NEW_LINE);
return result.toString();
}
public boolean equals( Object aThat ) {
if ( this == aThat ) return true;
if ( !(aThat instanceof Board) ) return false;
Board that = (Board)aThat;
return
EqualsUtil.areEqual(this.fID, that.fID) &&
EqualsUtil.areEqual(this.fLanguage, that.fLanguage) &&
EqualsUtil.areEqual(this.fTimeZone, that.fTimeZone) &&
EqualsUtil.areEqual(this.fTitle, that.fTitle);
}
public int hashCode() {
int result = HashCodeUtil.SEED;
result = HashCodeUtil.hash( result, fID );
result = HashCodeUtil.hash( result, fTitle );
result = HashCodeUtil.hash( result, fLanguage );
result = HashCodeUtil.hash( result, fTimeZone );
return result;
}
// PRIVATE //
private final String fID;
private final String fTitle;
private final Language fLanguage;
private final String fTimeZone;
private static final Pattern fIDPattern = Pattern.compile("[A-Za-z]{3,32}");
private static final Pattern fTitlePattern = Pattern.compile("(\\S)(.){0,49}");
private static final Pattern fTimeZonePattern =
Util.getPatternFromList(new TimeZoneChoices().getList());
private void validateState() throws VOException {
VOException ex = new VOException();
if ( ! isValidID(fID) ) {
ex.add("ID contains only letters, length in range 3..32, and is unique: " + fID);
}
if ( ! isValidTitle(fTitle) ){
ex.add("Title starts with a visible character, length in range 1..50: " + fTitle);
}
if ( ! isValidLanguage(fLanguage) ){
ex.add("Language must be non-null.");
}
if ( ! isValidTimeZone(fTimeZone) ){
ex.add("Time zone must compatible with java.util.TimeZone, " +
"and must be among those specified in web.xml: " + fTimeZone);
}
if ( ! ex.isEmpty() ) throw ex;
}
private boolean isValidID(String aID){
return Util.matches(fIDPattern, aID);
}
private boolean isValidTitle(String aTitle){
return Util.matches(fTitlePattern, aTitle);
}
private boolean isValidLanguage(Language aLanguage){
return aLanguage != null;
}
private boolean isValidTimeZone(String aTimeZone){
return Util.matches(fTimeZonePattern, aTimeZone);
}
}
-- JohnOHanley - 19 Jul 2003
|