Home | FAQ | Contact me

Interface Use

Here's a useful and simple way to capitalize on Java interfaces. Interfaces have myriad uses in Java; this is only one.

Imagine you've got two entities with a small subset of fields in common that you need to write validators for. You don't want to disturb the perfection of the entity classes by subclassing, but you only want to write the validators once.

Gather the accessors for those common fields in one place, an interface. In this example, those fields would be stuff related to language and country settings, currency, etc.

ElementValidation.java:
public interface ElementValidation
{
    public String getCurrency();
    public void   setCurrency( String currency );
    public String getIpaddress();
    public void   setIpaddress( String ipaddress );
    public String getLanguage();
    public void   setLanguage( String language );
    public String getState();
    public void   setState( String state );
    public String getCountry();
    public void   setCountry( String country );
    public String getZipcode();
    public void   setZipcode( String zipcode );
}

Then, implement this interface in the consuming classes. If any of the fields are not actually in common, the accessors can be supplied to meet the contract, they just don't do anything. And, when validating, either the validator doesn't call the accessor because it's not validating that element, or it checks the returns so as not to blow chunks.

I use the @Override annotation, not because Java actually requires it, though it is "academically" proper, but to mark visually the accessors that are involved in this scheme.

Address.java:
public class Address implements Serializable, ElementValidation
{
    private String oid;
    private String fullname;
    private String street1;
    private String street2;
    private String street3;
    private String city;
    private String state;
    private String country;
    private String zipcode;
    private String language;

    public String getOid() { return this.oid; }
    public void   setOid( ObjectId oid ) { this.oid = oid; }
    public String getFullname() { return this.fullname; }
    public void   setFullname( String fullname ) { this.fullname = fullname; }
    public String getStreet1() { return this.street1; }
    public void   setStreet1( String street1 ) { this.street1 = street1; }
    public String getStreet2() { return this.street2; }
    public void   setStreet2( String street2 ) { this.street2 = street2; }
    public String getStreet3() { return this.street3; }
    public void   setStreet3( String street3 ) { this.street3 = street3; }
    public String getCity() { return this.city; }
    public void   setCity( String city ) { this.city = city; }

    @Override public String getState() { return this.state; }
    @Override public void   setState( String state ) { this.state = state; }
    @Override public String getCountry() { return this.country; }
    @Override public void   setCountry( String country ) { this.country = country; }
    @Override public String getZipcode() { return this.zipcode; }
    @Override public void   setZipcode( String zipcode ) { this.zipcode = zipcode; }
    @Override public String getLanguage() { return this.language; }
    @Override public void   setLanguage( String language ) { this.language = language; }
    @Override public String getCurrency() { return null; }
    @Override public void   setCurrency( String currency ) { }
    @Override public String getIpaddress() { return null; }
    @Override public void   setIpaddress( String ipaddress ) { }
Payment.java:
public class Payment implements Serializable, ElementValidation
{
    private String  oid;
    private String  currency;
    private String  ipaddress;
    private String  language;
    private String  street1;
    private String  street2;
    private String  street3;
    private String  city;
    private String  state;
    private String  country;
    private String  zipcode;
    private Integer cardtype;
    private String  cardholder;
    private String  cardnumber;
    private String  expmonth;
    private String  expyear;
    private String  phone;

    public String getOid() { return this.oid; }
    public void   setOid( ObjectId oid ) { this.oid = oid; }

    @Override public String getCurrency() { return this.currency; }
    @Override public void   setCurrency( String currency ) { }
    @Override public String getIpaddress() { return ipaddress; }
    @Override public void   setIpaddress( String ipaddress ) { }
    @Override public String getLanguage() { return this.language; }
    @Override public void   setLanguage( String language ) { this.language = language; }

    public String getStreet1() { return this.street1; }
    public void   setStreet1( String street1 ) { this.street1 = street1; }
    public String getStreet2() { return this.street2; }
    public void   setStreet2( String street2 ) { this.street2 = street2; }
    public String getStreet3() { return this.street3; }
    public void   setStreet3( String street3 ) { this.street3 = street3; }
    public String getCity() { return this.city; }
    public void   setCity( String city ) { this.city = city; }

    @Override public String getState() { return this.state; }
    @Override public void   setState( String state ) { this.state = state; }
    @Override public String getCountry() { return this.country; }
    @Override public void   setCountry( String country ) { this.country = country; }
    @Override public String getZipcode() { return this.zipcode; }
    @Override public void   setZipcode( String zipcode ) { this.zipcode = zipcode; }

    public Integer getCardtype() { return this.cardtype; }
    public void    setCardtype( Integer cardtype ) { this.cardtype = cardtype; }
    public String  getCardholder() { return cardholder; }
    public void    setCardholder( String cardname ) { this.cardholder = cardname; }
    public String  getCardnumber() { return cardnumber; }
    public void    setCardnumber( String cardnumber ) { this.cardnumber = cardnumber; }
    public String  getExpmonth() { return this.expmonth; }
    public void    setExpmonth( String expmonth ) { this.expmonth = expmonth; }
    public String  getExpyear() { return this.expyear; }
    public void    setExpyear( String expyear ) { this.expyear = expyear; }

Now, the validators are a simple thing to write and all in one place. Here, also, are two validation methods used that aren't part of this scheme, but work for both types of entities. These methods are called from our business logic high above entities and DTOs.

There's some other stuff going on in here such as throwing an application exception in case of failure, but this is easily observed and separate from the point of this topic. In validation, there's a distinction between something missing and something invalid as reflected here.

The Validation calls go down to the DTO and database to look up an entity if necessary to see if it's there.

Validation.java:
public class ElementValidator
{
    private static int inspectCurrency( ElementValidation element, AppException e )
    {
        int    err;
        String currency = element.getCurrency();

        if( currency == null )
        {
            // in US we default this...
            element.setCurrency( EntityConstants.CURRENCY_DEFAULT );
            return 0;
        }

        err = Validation.validateCurrency( currency, e );

        if( err == 0 )
            return 0;

        e.addInvalidField( "currency" );
        return err;
    }

    private static int inspectLanguage( ElementValidation element, AppException e )
    {
        int    err;
        String language = element.getLanguage();

        if( language == null )
        {
            // in US we default this...
            element.setLanguage( EntityConstants.LANGUAGE_DEFAULT );
            return 0;
        }

        language = language.toLowerCase();
        err = Validation.validateLanguage( language, e );

        if( err == 0 )
        {
            element.setLanguage( language );
            return 0;
        }

        e.addInvalidField( "language" );
        return err;
    }

    private static int inspectCountry( ElementValidation element, AppException e )
    {
        int    err;
        String country = element.getCountry();

        if( country == null )
        {
            // in US we default this...
            element.setCountry( EntityConstants.COUNTRY_DEFAULT );
            return 0;
        }

        country = country.toUpperCase();
        err = Validation.validateCountry( country, e );

        if( err == 0 )
        {
            element.setCountry( country );
            return 0;
        }

        e.addInvalidField( "country" );
        return err;
    }

    private static int inspectState( ElementValidation element, AppException e )
    {
        int    err;
        String state   = element.getState();
        String country = element.getCountry();

        if( state == null && Validation.needsState( country ) )
        {
            e.addMissingField( "state" );
            return 1;
        }

        state = state.toUpperCase();
        err = Validation.validateState( state, e );

        if( err == 0 )
        {
            element.setState( state );
            return 0;
        }

        e.addInvalidField( "state" );
        return err;
    }

    private static int inspectIpaddress( ElementValidation element, AppException e )
    {
        String ipaddress = element.getIpaddress();

        if( StringUtil.isEmptyOrNullLiteral( ipaddress ) )
        {
            e.addMissingField( "ipaddress" );
            return 1;
        }

        try
        {
            new DottedQuad( ipaddress );
        }
        catch( IllegalArgumentException e1 )
        {
            e.addInvalidField( "ipaddress" );
            return 1;
        }

        return 0;
    }

    private static int inspectName( String name, String fieldname, AppException e )
    {
        if( StringUtil.isEmptyOrNullLiteral( name ) )
        {
            e.addMissingField( fieldname );
            return 1;
        }

        return 0;
    }

    private static int inspectDate( String date, int length, String fieldname, AppException e )
    {
        if( StringUtil.isDecimal( date ) && date.length() == length )
            return 0;

        e.addInvalidField( fieldname );
        return 1;
    }
}