Chan Chan - 9 months ago 38
Java Question

What's good practice of creating input validation method in Java?

If I want to validate my input, should I make validation code as private helper methods or create a separate static helper class? Does the validation code increase the size of the object?

More Information

Let's say I have a class

import java.util.Vector;


public class Place {
private final double longitude;
private final double latitude;
private final String id;

private String address;
private String name;
private String types;
private String icon;
private String phoneNumber;
private String websiteUrl;
private int rating;
private Vector<Integer> challenges;

public static class Builder {
// required parameter
private final double longitude;
private final double latitude;
private final String id;
// optional parameter
private String address = "n/a";
private String name = "n/a";
private String icon = "n/a";
private String phoneNumber = "n/a";
private String websiteUrl = "n/a";
private String types = "n/a";
private Vector<Integer> challenges = new Vector<Integer>();
private int rating = 0;

public Builder(double longitude, double latitude, String id) {
assert(longitude >= -180.0 && longitude <= 180.0);
assert(latitude >= -90.0 && longitude <= 90.0);
this.longitude = longitude;
this.latitude = latitude;
this.id = id;
}

public Builder address(String address) {
this.address = address;
return this;
}

public Builder types(String types) {
this.types = types;
return this;
}

public Builder name(String name) {
this.name = name;
return this;
}

public Builder icon(String icon) {
this.icon = icon;
return this;
}

public Builder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}

public Builder websiteUrl(String websiteUrl) {
this.websiteUrl = websiteUrl;
return this;
}

public Builder builder(int rating) {
this.rating = rating;
return this;
}

public Place build() {
return new Place(this);
}
}

public Place(Builder builder) {
// required parameters
longitude = builder.longitude;
latitude = builder.latitude;
id = builder.id;

// optional parameters
address = builder.address;
types = builder.types;
name = builder.name;
icon = builder.icon;
phoneNumber = builder.phoneNumber;
websiteUrl = builder.websiteUrl;
rating = builder.rating;
challenges = builder.challenges;
}

public double getLongitude() {
return longitude;
}

public double getLatitude() {
return latitude;
}

public String getId() {
return id;
}

public void setAddress(String address) {
this.address = address;
}

public String getAddress() {
return address;
}

public String getTypes() {
return types;
}

public void setTypes(String types) {
this.types = types;
}

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setIconUrl(String icon) {
this.icon = icon;
}

public String getIcon() {
return icon;
}

public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}

public String getPhoneNumber() {
return phoneNumber;
}


public void setWebsiteUrl(String websiteUrl) {
this.websiteUrl = websiteUrl;
}

public String getWebsiteUrl() {
return websiteUrl;
}

public void setRating(int rating) {
this.rating = rating;
}

public int getRating() {
return rating;
}

@Override
public String toString() {
return "(" + Double.toString(longitude) + ", " + Double.toString(latitude) + ")";
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Place other = (Place) obj;
if (id == null) {
if (other.id != null)
return false;
}
else if (!id.equals(other.id))
return false;
return true;
}

public Vector<Integer> getChallenges() {
return new Vector<Integer>(challenges);
}

public void addChallenges(Integer i) {
this.challenges.add(i);
}

public void showChallenges() {
for (Integer i : challenges) {
System.out.print(i + ", ");
}
}
}


If I have to validate
address
argument before setting it, where should I put the code for validating address in this case?

Answer Source

If you are talking just seeing if the entered String is formatted correctly or if the length is right, then you would use a private method. If you would on the other hand check if the address is correct (look it up on a map) or any more advanced stuff, it would make sense to create a AddressValidator interface and call it from that private method.

The reason for the private method being that you call this both from a constructor, setter or any other method that could suppy an address. The reason for the interface being that you might want to have e.g. an online / offline AddressValidator (MockAddressValidator, or one that calls a different class for each country etc).

As an AddressValidator could be reused in other classes, and to keep your code clean, I would create it as a top level interface + OnlineAddressValidator. This makes your class better readable as well. For full configurability, you might want to think about how you are going to supply the AddressValidator instance, e.g. through the constructor or one defined as a static final validator.

public interface AddressValidator {
    static class AddressValidatorResult {
        // some results, you might want to return some useful feedback (if not valid)

        boolean isValid() {
            throw new IllegalStateException("Method not implemented yet");
        }
    }

    public static class AddressValidationException extends Exception {
        private AddressValidationException(AddressValidatorResult result) {
            // add some implementation
        }
    }


    // don't throw ValidateException here, invalid addresses are normal for
    // validators, even if they aren't for the application that uses them
    AddressValidatorResult validateAddress(String address);
        // don't throw ValidateException here, invalid addresses are normal for
        // validators, even if they aren't for the application that uses them
}

public class DefaultAddressValidator implements AddressValidator {

    public static class Params {
        // some parameters for this specific validator
    }

    private final Params params;

    public DefaultAddressValidator(Params params) {
        // creates this validator
        this.params = params;
    }

    @Override
    public AddressValidatorResult validateAddress(String address) {
        // perform your code here

        // I don't like "return null" as it may lead to bugs
        throw new IllegalStateException("Method not implemented yet");
    }
}


// and use it like this
private void validateAddress(String address) throws AddressValidationException {
    // e.g. field AddressValidator set in constructor 
    AddressValidatorResult result = addressValidator.validateAddress(address);
    if (!result.isValid() {
        throw new AddressValidationException(result);
    }
}