The topic of this article is encapsulation in programming. We'll cover the following:
- what encapsulation is
- why encapsulation is useful
Encapsulation is a powerful technique that helps you avoid bugs and modularize your programs for easier development. It is especially important when you're working with other developers or if you are authoring an open source library for others to use.
There are various definitions for encapsulation floating around the internet. However, I think the most useful one is the following:
Encapsulation is a programming technique for restricting direct access to a component's internal state.
Direct access means that you can't directly reassign an encapsulated variable. You can only change it via the public API of the component. If it isn't obvious what I mean by this keep reading - we'll look at some examples later in this article.
You can use encapsulation to protect a component from getting to an invalid state. It does this by protecting access to the data that defines the component's state.
What is an invalid state?
What counts as an invalid state depends on the component, of course, but let's say we have a clock
component that has two methods:
Let's say we call
clock.getTime() and it returns 9:60. Obviously,
that is an invalid state for a clock to be in. There are only 60 minutes in an
hour. 9:59 should be followed by 10:00.
How do you prevent components from getting to invalid states?
To ensure that the clock will never get to this invalid state we add some
checks in the
advance() method to make sure that the minutes and the hours are
handled correctly. It could look something like this.
Now, here's the catch: for this to work we have to hide
this.minutes from the
user. Otherwise they can break this code by simply setting the minute property
to 60. The purpose of encapsulation is to make this impossible.
By letting the user only change the state via a public API we can ensure that the component cannot get to an invalid state unless we have a bug in our API. This will greatly diminish the number of possible bugs, which should be one of the primary concerns of any programmer.
Why does this matter?
While the example above may seem contrived, it's good to remember that the best way to avoid bugs is to make them impossible. People don't generally care how you intent your code to be used. They want to get their job done and if modifying the internal state makes it easier then they'll do just that. And when your component breaks they'll most likely blame you regardless of whether they used your code "incorrectly". Let's also keep in mind that, given enough time, you will also forget how to correctly use your own code.
Most programming languages offer some way of encapsulation. Usually, its classes with private properties and methods. The keyword here is private. It means that those properties can only be accessed from within the class itself and are therefore not accessible to outside users.
Please note that lacking support for encapsulation doesn't mean that a given technique is always incorrect or that you should never use them. We are only looking at these from the perspective of encapsulation.
✕ ES6 classes
Clock class. You can declare a class by using the
give it a constructor and declare some variables. The problem is that there is no
way to make these variables private. Therefore, everything you attach
this can be accessed by anybody.
✕ Binding functions to the Prototype
Then, there's the ES5 way where you attach the methods to the object's prototype and members to the object itself. This is the same as ES6 classes but without the syntactic sugar. It, too, is utterly useless for encapsulation.
✕ Using underscore ( _ ) to mark private variables and methods
Prepending private methods and properties with an underscore (
_) is very
common. This is not encapsulation, it's just a naming convention. Though better than nothing, I find it very difficult to stick to
conventions like this. Moreover, users of your code may not be familiar with this
To use this feature for encapsulation you can create a function that returns another function. Declare the private variables in the outer function and access them from the inner function.
Let's revisit the clock component from above and implement it using a closure:
Let's see how we can break encapsulation even when using closures:
The problem here is the
getState function. You see how we just hand out the
internal state to whomever asks for it. This immediately breaks encapsulation.
One should only be able to modify the inner state by calling the object's
methods. Now that we've leaked the inner state it can be set to an invalid state
from the outside.
This leads us to an important rule:
Public methods of an encapsulated component should only return representations of the component's internal state, not references to the state itself.
In this corrected version we return a copy of the object's position. This way we don't leak that internal state and can prevent it from being modified incorrectly.
Notice that we didn't talk about when to use encapsulation. This is too difficult a question to fit in the scope of this article but we will definitely explore it in future tutorials.