What is Encapsulation anyway?
Way too much effort is expended on making things private or public and arguing what is abstract or an interface without a clear intent behind *why* and *when* such practices are favorable
Hey it’s Denis. This is Part 2 of a series called Software Engineering Fundamentals to give you a strong base of knowledge for 2024 to build from over the next few weeks. The series has a strong overlap with our live streams on WED/THU.
💡 The big lightbulb moment is at the end of this article.
What is Encapsulation?
Imagine your most primitive building block: a pure function.
In computer programming, a pure function is a function that has the following properties:
the function return values are identical for identical arguments
the function has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or input/output streams).
A function that sums up numbers you pass it is pure. A function that caches (memoization) the already-returned sums is not.
At least… according to the above definition from wikipedia. However, most pragmatic engineers will disagree with that notion, especially when it comes to the second condition’s interpretation of “local static variables”.
If I got it wrong and you have a better reference-definition, let me know!
Encapsulation is the hiding of detail—the state or implementation. It allows you to think of your building blocks as small computers, without needing a domain-specific science degree to understand how it works.
Encapsulation replaces our need for knowledge of the encapsulated data with a type of the encapsulator—a cell. This type of the encapsulator is often referred to as an interface. However, don’t confuse it for classes or interfaces in the Java-sense of the meaning. We care about methods of behavior. What can the cell do?
Organisms, not inheritance
Each cell in your system responds to the liquid it is surrounded in. The idea of encapsulation is such that no other cell or larger tissue can reach into this cell and tinker with it. That would essentially be a violation of its boundaries, destroying the guarantees it provides (ie. its lifecycle or detail).
Message buses and event-handling patterns are what complete such a typed, encapsulated system. The messages surrounding each cell is what makes them produce new signals, without requiring coupling to the sender.
There are many different paradigms for this: async queues, synchronous queues, pubsub, actors. We won’t go over all of them today.
Let’s look at the basic building blocks instead: functions and objects.
Coupling
When you encapsulate state (hide it), the owner has to be coupled to the outside world in some way to make meaningful use of the encapsulated data. In most languages being used today these cells are forms of objects (classed or classless) or functions (higher or lower).
A classes object is one that has an explicit class definition.
A higher-order function is one that returns a lower-order one.
This is what makes object-oriented paradigms so similar to functional programming. This is also what makes inheritance such a slippery slope. Inheritance is a simple way of imperative code-reuse, nothing more.
Mental shift
The following mental shift occurs when taking these ideas to the architectural and team-organization level:
“Rather than coupling to my data, couple to the behavior it enables.” Rather than getters that return copies of the data, you expose methods that operate on the data.
What makes Getters evil?
Here’s a simple example.
Let’s say you have a cell to display your bank account’s balance. The naive OOP programmer may be tempted to create a BankAccount class, whose objects will provide their getBalance
copies or getTransactions
.
But as a technologist who understands encapsulation you’ll immediately spot the problem: I don’t want to change my simple UI cell every time we change the heuristics of how accounts are saved.
So rather than employ getters, you expose an interface for showing the balance, ie. printBalance(). How you name that construct it lives on doesn’t matter, and languages like typescript and go even allow you to focus on the structure without naming.
Notice what happened. Rather than reaching into the cell, we signalled the cell’s boundary to re-shape its data in a domain-specific meaning. In this case print and balance have a specific meaning in this micro-context.
Law of Demeter
The Law of Demeter (LoD) or principle of least knowledge is a design guideline for developing software, particularly object-oriented programs.
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
Each unit should only talk to its friends; don't talk to strangers.
Only talk to your immediate friends.
Law of Demeter is where things get hairy. You might know it as the one-dot-rule, ie. no object chaining, like this user.account.balance.fetch().print()
. It is a concept mentioned as far back as 1987.
However the one-dot-rule is an oversimplification of the above definition. Law of Demeter is about types not objects. Here is the correct definition for classed languages from Object-Oriented Programming: An Objective Sense of Style (thank you Yegor):
For all [classes] C, and for all [methods] M attached to C, all [objects] to which M [sends a message] must be instances of [classes associated with the following classes]: 1) the [argument classes of M] (including C), 2) the [instance variable classes] of C. emphasis mine
This makes things clearer! To paraphrase in simpler language:
Class C knows types and classes inside its cell and those that touch its membrane (method arguments).
It must not call methods or pass messages to types and classes it does not know.
Which means method chaining with builder or factory patterns is not a violation, even though there are multiple chains in the object graph. In the above bank statement example it may also be alright if the class has friendly knowledge of all the classes being used in the chain!
This last bit about Law of Demeter in context of OOP encapsulation is what connects the dots for my students in various combination of exclamation, confusion and enthusiasm.
Join the Live discussion
Want more? I’ll be doing some practical examples about this topic today live on stream. See you soon!
It's easy to claim you know what encapsulation is. Hiding things. Using classes.
But that's just learning the tools without understanding the key ideas behind objects.
Get a few key details wrong and your codebase ends up being a mess of needless layering and injection. By the end of it you'll be wishing for the procedural simplicity of C.
So on this Our Tech Journey stream, the first engineering-focused one in 2024, we'll go over
fundamentals of encapsulation
differences in state and data in OOP, functional, procedural and similar paradigms
architectural patterns that speed up your team when OOP is done this way.