The Importance of Code Encapsulation

Redan Hassoun
The Startup
Published in
8 min readOct 13, 2020

Most of the developers, when they hear about encapsulation, they think of it as one of the fundamental concepts in object-oriented programming, and it means that the class (or any component of code) exposes only the relevant data to the outside world and hides the internal data as private members, but this principle is more general and powerful than a lot of developers think, and although it is defined in OOP, you can still apply it one way or another in other programming paradigms.

So, what is the encapsulation all about? What does it really mean, and why do we need it?

According to Wikipedia: “Encapsulation is used to hide the values or state of a structured data object inside a class, preventing unauthorized parties’ direct access to them. Publicly accessible methods are generally provided in the class (so-called “getters” and “setters”) to access the values, and other client classes call these methods to retrieve and modify the values within the object.”

But what does this really mean? When do we hide data\implementation, how, and for what purpose?

Let’s take a closer look at the encapsulation principle, this principle has two main important ideas:

1. Information Hiding:

What information hiding means is separating the interface of some code component (a function\class\module\library\ …) from its own internal implementation, the implementation details should be private and not accessible from outside, any changes to the state is done only using the public API (which is a set of functions\methods that defines what our code can do). The public API should be clear and enable the code users to use it without having to know the internal details. For example, if we have a UI library that helps us render a table of data on our page, if the library is well encapsulated, the developers who use the library doesn’t have to know everything about the internal implementation (which data structure is used to save the data, which algorithm is used to render and update the data, which mechanism is used for pagination and all other internal details, a.k.a: implementation details).

The client (developer) should have a clear and simple API that enables him to use the library without the need to read the internal code, the developer has to see only the things he needs to know about in order to operate that library, not less and not more.

So we can say that information hiding is defining a simple public interface which defines what operations our code can do, and “hide” the implementation of this interface internally, and every piece of logic that is related to that implementation in one place instead of writing code that is scattered around.

In order to do this properly, it is very important to define a good API (interface) for our code, but what is a good API?
In order to better sense this concept, let’s take the following method as an example:

String handleResponse(String response) {

}

Is it possible to know exactly what the function does without peeking into the code?

There are a couple of questions that arise when looking at this function, what is the meaning of the String return value? what should I do if the function returns null? what does exactly the function do to the response? Does it perform any side effects? …

Sometimes it is possible to read the code’s documentation in order to know how the methods\classes should be used, but we don’t always have documentation, or sometimes the documentation is written poorly or incomplete.

So the method signature plays a huge role when it comes to encapsulation and information hiding, and it should answer all the basic questions like what it does and how it is used.

These questions should be answered by the method’s signature (method name, the parameters it accepts, and the return value).

In order to fix the method in the previous example, the first thing that should be done is giving it a better name, the name should be a simple verb that describes exactly what the function performs (note that the function should perform only one thing and do it well — for more information read about the single responsibility principle). Also, it is better to use a data-type that describes the function’s parameter in an accurate way. And lastly, it is very important to choose the correct return type, if the function only performs a simple side effect then maybe there is no meaning for a return value. And if it must return something, the return type should be precise and prevent any ambiguity.

2. The protection of the invariants:

An invariant in general is a property of the program state that is always true, or more precisely a predicate that defines the valid state of an object, this predicate is expected to be maintained by the object’s operations and should be always valid.

For example, the “hasNext” method of the collection iterator returns false if and only if we have exhausted all of the collection’s elements, this invariant must always be true no matter when we call the method, no matter which method we call on our collection, that invariant will still be valid (the worst thing that can happen is that we perform an invalid operation, but in that case, an exception will be thrown as early as possible with useful information about the error).

So what “protection of the invariants” means is that our code should make sure the invariants are always true, and it is very difficult (even impossible) to put an object in an invalid state.

Let’s take a simple Java code example to illustrate this concept, imagine a class that represents a phone book:

class PhoneBook {   private List<Person> peopleList;   public void init(List<Person> peopleList) {      this.peopleList = peopleList;   }   public Person get(int id) {    ...
}
public void add(Person person) { ...
}
public int getCount() { return this.peopleList.size(); }}

And consider this usage:

PhoneBook phoneBook = new phoneBook();phoneBook.getCount();

Looks like the “getCount” method will throw an exception (because the init method was not called), so it was very easy to misuse the PhoneBook object and put it in an invalid state which means this object does not properly protect its invariants so it is not well encapsulated (in this case the developer who uses this class should look into the implementation in order to know how to use it properly), of course, this is only a simple example to illustrate the concept, in the real world, the code can be much more complex and if the code is written poorly, mistakes can waste a lot of time.

The simple solution for this problem would be to initialize the people list on the constructor and make sure to throw an exception if the object is created with a null value, that exception should have a clear message that explains exactly what went wrong (and never blame the user).

This leads us to the “fail-fast mechanism”, which is a mechanism that helps us build systems that report errors as early as possible rather than attempting to proceed with an incorrect behavior, and write error messages in a way that can help the client know what to do to avoid that error.

In addition, if an object has a value which is an invariant that is maintained by that object’s operations, we should prevent changing that value directly from outside the class, because if we do this the wrong way, we may end up with an invalid state on that object, for example, let’s say that this PhoneBook class tracks the count of phone numbers that is divisible by 3, if the class has a method that returns a reference to the “peopleList”, the users of this class can add/remove elements from the list without updating the count of the numbers that is divisible by 3, which brings the object to an invalid state, this issue will lead to unexpected bugs.

So in short, we should design our classes in a way that they manage their state properly and make sure that the users of our code cannot easily reach an invalid state, and if they do something wrong, they should get early feedback with a proper error message.

Why encapsulation is important

There are tons of resources out there that can help you learn encapsulation in more depth,

But, why is this so important?

The encapsulation principle is very powerful and has a couple of benefits, let’s talk about the most important ones:

  • When maintaining code, encapsulation helps us protect many parts of the program from extensive modification if the design decision is changed, once we hide our implementation from its client and do it properly, changing that implementation will not affect the client’s code, which can help us avoid the risk of breaking things that are not related to the change we are making.
  • One more point about maintainability, a well-encapsulated code is much easier to read, understand with minimum waste of time. Imagine if you are supposed to add a feature on top of code that you are not familiar with, and you want to reuse that code, if the code is not encapsulated well, you may end up reading most of the functions/classes before you use them, and be sure to manage their state properly, so this can cause developers to waste a lot of time reading the current code, instead of writing new code.
  • As mentioned earlier, encapsulation also protects the integrity of our code, and this is done by not allowing the client to modify the internal data and put the component in an invalid state, which makes the code more safe and reliable, in addition, if the fail-fast mechanism is used properly, developers can detect failures early and get enough information about the error and how to avoid it.
  • It helps us reduce the complexity of our design, by reducing the tight coupling between different components in our code, and it can help reduce spaghetti code and also helps developers to be more productive.
  • Encapsulation also makes it easier to unit-test our code, because it helps us encapsulate and manage all the properties and methods related to each other in one place, which makes it easier for us to test them.

Conclusion

To wrap up, encapsulation is one of the fundamental concepts on OOP, and in programming in general, it is very important because it helps us write readable code and maintainable systems.

One more important thing I want to point out, that even if you are not using OOP this doesn’t mean you don’t want to apply encapsulation in one way or another, because you will still need to write reusable components, and you will usually write features on top of these reusable components, and even if these components are not classes with a “hidden” state, it is still important to write your code in such a way that you encapsulate related pieces of code into one place and write it in a way that you spare other developers from digging into all the details of your code in order to be able to use it, and also prevent misuse as much as you can.

And more importantly, write a code that even if it is used the wrong way, it gives its user proper feedback about the error and how to avoid it.

So what do you think? Do you also think encapsulation is very important?

--

--