Problem 227

Encapsulation

Why

Imagine you have a class:

class Person {
  String name;
  int age;
  
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

… and programs that use your class implement it like:

Person p = new Person("Frank Smith", 39);
if (p.age > 18)
  System.out.println(p.name + " can vote!");

Then, one day you say to yourself, “hmm, I should store the first and last name separately”.

class Person {
  String firstName;
  String lastName;
  int age;
  
  ...
}

Now we introduced two issues with how the class was initially implemented. The first issue is more important than the second (for now)

Person p = new Person("Frank Smith", 39);
if (p.age > 18)
  System.out.println(p.name + " can vote!");
  1. p.name is no longer a vald way of accessing the data we need.
  2. Our class’s constructor may need reworking and/or overloading.

Encapsulation doesn’t break the public interface

The public interface is how other programs interact with our class. In this case, we allowed the outside program to directly access the name field using the .name attribute. The minute we made a change to how the class represented it’s information, it broke our public interface. Every program that implemented your class will be broken. Governments, corporations and other human beings would now hate you.

If, however, we created our class with proper encapsulation, this issue would have been avoided.

Getters and Setters

Let’s apply the same changes, but using proper encapsulation.

class Person {
  private String name;
  private int age;
  
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  
  public string getName() {
    return name;
  }
  
  public int getAge() {
    return age;
  }
}
  1. You will notice I added two “getters”; getAge() and getName(). Getters get stuff. Alternatively setters set stuff. We no longer set and get object fields by directly accessing them.
  2. We make the object fields private to prevent any direct access. If we allow for public access of fields, we open the door to the possibility of a broken public interface in the future.

Now the code to implement this class will be as follows:

Person p = new Person("Frank Smith", 39);
if (p.getAge() > 18)
  System.out.println(p.getName() + " can vote!");

Now that we have ensured that programs must access our object’s fields through a mediator, we can implement the name changes, firstName and lastName without breaking anyone else’s programs. We also correct issue #2 from above by adding different constructor (overloading).

class Person {
  String firstName;
  String lastName;
  int age;
  
  // old constructor method
  public Person(String name, int age) {
    String name = "Frank Smith";
    String[] parts = name.split(" ");
    this.firstName = parts[0];
    this.lastName = parts[1];
    this.age = age;
  }
  
  // overloaded constructor method (new implementation)
  public Person(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
  
  public string getName() {
    return firstName + " " + lastName;
  }
  
  public int getAge() {
    return age;
  }
}

As you can see, even though we made the exact same change to our class, using encapsulation prevented the public interface from breaking. Because we now have overloaded constructor methods, we can accept the old way of instantiating the object or the new way.

Extra reading: Why Encapsulation Matters


◄ 226: Dog Constructor 228: Encapsulate! ►