6

Let's say we use Java SE (no libraries) and have the following situation. We have a class:

public class DriverInfo {
    private final int age;

    public DriverInfo(int age) {
        this.age = age;
    }

    // getter here
}

In some countries there is a requirement that you can drive if you're 18 years old. In other words - we need to have some validation for age parameter. Something like:

if (age < 18) {
    throw new IllegalArgumentException("Age is not valid!");
}

So, my question is - where this validation should be? My thoughts are as follows:

  • On constructor add the if() described above. The validation would work if the constructor is called from multiple places on your code. My concern is that class constructors should (IMHO) not contain any logic. Am I right?
  • Validate somewhere outside the constructor - whether it's a factory method, builder class etc. But then we are forcing developers to not instantiate the class by using the constructor, but to use some artificial factory/builder.
  • To validate constructors parameters by using Validate. It's not standard Java, but I saw people doing this. As for me it doesn't look right, because we're adding logic to the constructor - which doesn't look right for me.
  • Any other good way I missed?

Any thoughts would be highly appreciated. Does anyone could suggest a best practice how to deal with the situation described abode?

Ernestas Kardzys
  • 1,719
  • 2
  • 16
  • 21
  • You should probably check the age before you even pass the value into the constructor. – Sweeper Mar 17 '18 at 10:48
  • 1
    It's perfectly fine to throw exceptions in the constructor. But before doing so you should release acquired resources, e.g. closing streams. – maraca Mar 17 '18 at 10:58
  • 2
    There are just too many, different approaches to this. Depending on how you run this code, the frameworks you use, you'll get different standard practices. Web frameworks, Java EE, etc. all give you ways to do this that don't even involve typing that `if` condition. You know your validation, what you're looking for is **the appropriate way to do it for your platform/framework**. If there's no standard approach for your "framework", then what you suggest is just perfect (except the hard-coding part) – ernest_k Mar 17 '18 at 11:03
  • 1
    See also: https://stackoverflow.com/questions/30803650/java-how-to-only-create-an-object-with-valid-attributes – Klitos Kyriacou Mar 17 '18 at 14:59
  • Ensure all your objects are constructed in a valid state by doing the validation in your constructor and throwing an exception when the object cannot be constructed in a valid state. It's perfectly fine to have logic in your constructor and it's done in many classes both in the Java API and in third-party libraries. For example [FileReader constructors](https://docs.oracle.com/javase/9/docs/api/java/io/FileReader.html#FileReader-java.lang.String-) read the file system to search for the file and ensure that the file is readable and then they open it. Otherwise they throw an exception. – Klitos Kyriacou Mar 17 '18 at 15:05
  • I tend to use Builders when I have to construct classes whose building requires validation. Constructor validation is perfectly fine as well but I just like keeping my constructor clean from validation. – Sneh Mar 17 '18 at 15:24
  • pedantic: a constructor specified parameters, at runtime they are called *arguments*. – Jasper Blues Mar 17 '18 at 15:32

4 Answers4

10

Validation in constructor is completely fine. This "no logic in constructor" rule does not apply on it, guess it's worded bit unfortunately. Task of constructor is to take dependencies from outside (dependency injection), make sure they are valid and then probably store them in instance attributes - creating valid instance in short.

This way the rules of what is valid and what is not valid are kept inside the object, not detached in some factory.

If the instance would be invalid, then throwing an exception, which explains what is wrong with parameters, is absolutely fine. It prevents invalid instances from roaming around system.

Also this way with immutable objects you get guaranteed that all existing instances are valid all the time, which is great.

Having some validation method on the object is possible and can be useful, but I'd prefer constructor validation any day. But if it's not possible, then it's better than nothing and it still keeps the rules for validity within the object. Like when some framework constructs your mutable object for you and requires parameterless constructor...you can forget to call it, but it's better than nothing.

Igand
  • 1,161
  • 1
  • 15
  • 25
1

I think you can "also" use Builder pattern for such use cases. Although Constructor validation is perfectly fine and usually Builder tend to introduce a lot of Boiler Plate code but if you want to avoid validation inside the constructor and also want to have immutable class (no setter validations) then you can give builders a try.

Usually builders should be used when faced with many constructor parameters. You can read more on builders here.

public class DriverInfo {

    private final int age;

    //more parameters here

    private DriverInfo(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public static DriverInfoBuilder newBuilder() {
        return new DriverInfoBuilder();
    }

    public static final class DriverInfoBuilder {

        private int age;

        private DriverInfoBuilder() {
        }

        public DriverInfoBuilder age(int age) {
            this.age = age;
            return this;
        }

        public DriverInfo build() {
            if (age < 18) {
                throw new IllegalArgumentException("Age is not valid!");
            }

            //other validations.

            return new DriverInfo(age);
        }
    }
}

Again there are many ways to do this and there is no right and wrong here. Its more about what one prefers and will it be readable for other programmers or not.

Sneh
  • 3,527
  • 2
  • 19
  • 37
0

I think there are two more techniques which you can use.

  1. Pass variables to super() and validate them at parent class. For example

    super(age)

  2. You can use getters, setters and validate the variable there. I tried using setters in java.

    public class validate_object_variable {
    private int age;    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        if(this.age >= 18)
        this.age = age;
        else {
            throw new IllegalArgumentException("Age is not valid!");
        }
    }
    
     public static void main(String args[]) {
            validate_object_variable obj = new validate_object_variable();
            obj.setAge(10);
            System.out.println(obj.getAge());
     }
     }
    

    This gives me the ability to cleanly exit with a valid exception to the caller.

    OUTPUT

    enter image description here I think it would not be a nice idea to throw exceptions in a constructor. But it's a good choice to validate the variables using setters separately without hampering the object creation.

Dinesh Kumar
  • 545
  • 4
  • 17
  • This answer assumes that the classes we are building are "mutable" which is bad. Secondly you want validation in super class which basically means extending a class just for the sake of validation. These techniques are not good. – Sneh Mar 17 '18 at 15:29
  • @Sneh i know super class technique depends upon weather child class have inherited from a parent other than object class or not? And I need clarification on how above code assumes that classes are "mutable" ? – Dinesh Kumar Mar 17 '18 at 17:57
  • Do you know what exactly mutability is? You are providing a setter to the class which allows “age” to be changed any time after construction. As for your super class solution, You are not fixing the problem but moving from one place to the other (assuming we are trying to avoid validation in constructor) – Sneh Mar 17 '18 at 21:30
-1

In my opinion, you should have a method inside the class DriverInfo, something like:

public boolean isDriverLegalAge(int age){
    if (age < 18) {
        return false;
    } else {
        return true;
    }
}

The constructor should only be used to build the object. Everything related to logic inside that object should be inside methods.

For more information regarding the purpose of a constructor see this link: https://stackoverflow.com/a/19941847/2497743