Tips & Tricks

Inspection Connection – Issue #5, Encapsulation Examination

Encapsulation is a simple but powerful concept in software engineering that often goes hand-in hand with abstraction. In OOP, we use these two principles to help model mundane and complex systems, from physical processes to digital transactions, however these terms can often mean different things to different people.

Encapsulation often refers to information hiding, a key principle of OOP that seeks to collect and contain common data and functionality, effectively hiding those internal components, while exposing methods to interact with them in a safe and well-defined manner. This reflects how we interact with the world around us.

For example, when singing a song, we don’t usually think about the sequence of nerve impulses required to hold a note or the resonant frequency of our vocal cords. When the choral director calls out, “Cantata in B flat major, bars 4-8”, everyone just opens their books and starts to sing – their training does the rest.

choir.sing(song);

Now imagine if the director had to transpose every note and teach her choir how to sound their Do-Re-Mis – there would be an uproar! Not only are mistakes more likely, it would be totally impractical to make even the smallest change in rhythm or tempo. Too many choices can paralyze any progress. This is the Centipede’s Dilemma.

tenor1.sing(song.bar4.getNote(1), key, startingFrom, duration, volume);

When designing APIs, it is important to consider the level of detail required at each layer of abstraction. By exposing lower-level details, we are not only creating unnecessary complexity for ourselves, but introducing the potential for confusion and misuse by others, which is why good encapsulation is so important. This is the principle of least privilege.

choir.getList().add(singer);

Here is a good example of poor encapsulation. Instinctively, we sense something is wrong: there are two things. First and foremost, getList() appears to return a mutable data structure. Never return a mutable data structure. Second, the callee should neither know, nor care about Choir‘s internal design. This is the principle of least knowledge.

choir.add(singer);

Yet encapsulation does not necessarily mean hiding our data under lock and key! In fact, good encapsulation is often more transparent as a result. By clearly identifying all the ways to interact with an object and codifying those patterns via explicit APIs, it becomes much easier to use. Give others a way, and they will take it: this is the principle of least effort.

public class Choir {
  public Singer leadVocal;
  public List<Singer> backupVocals;
}

If a class leaks unnecessary data about its internal state, those features can interact in surprisingly harmful ways. For example, sometimes it is tempting to access the internal state of an object by making it public. But that state may be changed by anyone who pleases, which can lead to nondeterminstic behavior. This is the principle of least surprise.

public static void swapVocalist(Choir c, Singer newLead) {
  Singer oldLead = c.leadVocal;
  choir.leadVocal = newLead;
  choir.backupVocals.add(oldLead);
}

Allowing mutable values to be visible outside a class, whether it is marked public, protected or default, invites consumers to modify the internal state of an object without fully understanding the state diagram. Perhaps leadVocal and backupVocals are not mutually exclusive. Perhaps the oldLead will dislike the newLead and throw him out.

public Choir(List vocals) {
  backupVocals = vocals;
  leadVocal = new Singer () {
    @Override
    public void sing(Song s) {
      if(Choir.this.leadVocal != this) {
        throwOut(Choir.this.leadVocal);
        Choir.this.leadVocal = this;
      }
    }
  }
}

The problem is, no one can tell by looking. And even if you have the full benefit of the implementation, who is to stop a malicious adversary from planting a tone-deaf singer in your nationally-acclaimed choir? There are many excellent reasons to use getters and setters, but if you must share state with other classes, proceed with caution.

IntelliJ IDEA offers many inspections for detecting these, and other types of encapsulation issues including access violations, visibility issues, returning mutable classes and many more. You can enable them under Settings|Inspections|Encapsulation Issues. Thank you for reading, and develop with pleasure!

image description