Types and Programming Languages Part 16 – Encapsulation and making all public

This is the sixteenth part of the Types and Programming Languages series. For your convenience you can find other parts in the table of contents in Part 1 — Do not return in finally

Let’s start with this iffy statement of mine — I believe everything should be public. No private modifier at all. And now, let’s discuss that a little more.

Encapsulation principle states that we should hide internals. This can take multiple forms — the most basic one is getters and setters. We make fields private, and then expose accessors for reading and writing. However, I generally tend to say that everything can be public, and we can still encapsulate. How is that possible?

There are multiple situations when we need to access some internals or to modify them. Either directly, or via reflection, or memory hacking. The problem is that most of the times compilers won’t help us in these situations. Compilers won’t check your reflection whether you have it right or not. They won’t stop your unsafe low-level code from compiling when it is incorrect. This makes it much harder to rely on internals, as they can be changed basically any time, and we don’t have a mechanism to find out that the change breaks our code. However, there is no such a problem when everything is public. If you accesses internals directly, then the compiler will let you know when there is a breaking change. This allows you to find errors much earlier and much easier.

But how can we encapsulate while having everything public? The answer is: views. With different views, which in most languages are implemented with interfaces, we can encapsulate and still be able to access internals. Imagine we have the following class:

One might say that this is good because it hides barInternals by making it private. This way the caller cannot access internals, and won’t break them. However, it’s better to use interfaces:

Now, any “decent” caller should use Foo via IFoo interface, so the method barInternals remains unavailable. It’s not that it’s private, or the access is blocked by the policy controller. The method simply doesn’t exist! However, once someone needs to go rogue, they can cast IFoo to Foo, and have compiler support to verify that barInternals is still there.

This can be implemented in multiple ways. Interfaces, header files, different packages for abstraction and implementation. There are multiple solutions for making the internals public.