Why Use Getters/Setters Instead of Public Fields?

TLDR; Because it makes it easier to change our code later

Getters and setters are methods that are used to read/write an object's private field. They might not seem useful since you can achieve the same task with public fields, but they are useful because encapsulation is useful. Let me explain.

As software engineers, our biggest challenge is not writing the least amount of code to accomplish a certain task — Unless we are doing code golf. Our biggest challenge is to deal with change; changes in requirements, frameworks, libraries, etc. Our ultimate goal is to edit the least amount of code when things change. Using public fields does result in shorter code, and probably won't cause any serious side effects in small programs, but it WILL in larger ones.

Large programs are notoriously difficult to change. There is no easy fix; software is inherently complex. However, by sticking to design principles and best practices such as encapsulation, we can certainly achieve better results

Encapsulation is a technique to reduce coupling between modules (AKA loose coupling). In other words, it is a method we use to minimize the number of units that needs to change as a result of changing one unit to fix a bug (for example). To use this technique you need to follow these rules:

  • Fields should be hidden from direct access (aka private)

  • Only methods in the same class can access/modify fields directly

  • A class can only be accessed by clients (that is, other classes that make use of this class) via its methods

Now, let's see a practical example where using encapsulation (accessing a class through its methods instead of direct access) can reduce the number of changes later.

class Counter {
    public int count = 0;
}
​
class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.count = 4;
        counter.count = 18;
        counter.count = 21;
        System.out.println(counter.count);
    }
}

Alright! Let's read the program together. First, we define a class named Counter with one instance variable count. Then, we create a Main class and inside it the main method. In the main method, we create a new instance of Counter named counter, then we set count to 3 different values and finally print the last value.

Unfortunately, requirements have changed slightly. We now have to print count every time its value changes. Here is the new code:

class Counter {
    public int count = 0;
}
​
class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.count = 4;
        System.out.println(counter.count);
        counter.count = 18;
        System.out.println(counter.count);
        counter.count = 21;
        System.out.println(counter.count);
    }
}

As you can see, we had to make 2 changes in order to address a minor requirement change. This may not seem like much because we only used count 3 times, but in a larger program, count will be used all over the place alongside other variables.

Alright! Let's introduce a setter and see if help us limit the scope of the change:

Before the change of requirements

class Counter {
    private int count = 0;

    public void setCount(int value) {
        count = value;
    }
}
​
class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.setCount(4);
        counter.setCount(18);
        counter.setCount(21);
    }
}

After the change of requirements

class Counter {
    private int count = 0;

    public void setCount(int value) {
        count = value;
        System.out.println("Count Changed: " + value);
    }
}
​
class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.setCount(4);
        counter.setCount(18);
        counter.setCount(21);
    }
}

As you can see, we have only made one change in the Counter class and no changes in the Main class. Besides requiring fewer changes, the new design is also scalable because the code remains the same regardless of how many times the count value changes. On the other hand, the old design requires updating the code, whenever count value changes.

Conclusion

Encapsulation is a powerful tool that is important to understand if you want to make use of OOP. The practice of using getters and setters as a means to enforce encapsulation may be controversial, but it can also be an effective solution if used judiciously. Ultimately, this blog is meant simply to enlighten you on the topic of encapsulation and show you one way you can use it in your code.