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:
1 2 3 4 |
class Foo { public void bar() { ... } private void barInternals() { ... } } |
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:
1 2 3 4 5 6 7 8 |
interface IFoo { void bar(); } class Foo implements IFoo { public void bar() { ... } public void barInternals() { ... } } |
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.