Quality Code Is Encapsulated

Quality code is encapsulated—it hides its implementation details from the rest of the world. One of the most valuable benefits of using an object-oriented language over a procedural language is its ability to truly encapsulate entities. By encapsulation, I don’t just mean making state and behavior private. Specifically, I want to hide interface (what I’m trying to accomplish) from implementation (how I accomplish it).

There are many things that can be encapsulated, and the process of writing a good program is the process of hiding as much as possible while still getting the job done. For our purposes, let’s redefine encapsulation to mean hiding what something does from how it does it. The more you can hide how you implement something, the more freedom you have to change the implementation later without affecting other parts of the code. This helps keep code more modular and simpler to work with.

Well-encapsulated software comes about by designing from the outside-in rather than the inside-out.

Outside-In Programming: Outside-in programming, as I call it, designs features from the consumer’s perspective. A service is designed based on the needs of the clients of that service. Name things after what the service does and hide how the service works. This helps create strong contracts between services that decouple components.

Inside-Out Programming: By contrast, the way most developers write software is to decompose the problem down into small pieces and stitch the pieces together to form a solution. I call this inside-out programming because the problem is decomposed to get right to the solution so developers can start coding. But when developers jump into implementation without first seeing the big picture, they tend to build brittle code whose responsibilities aren’t well defined, making it harder to encapsulate.

Ultimately, developers need to see their code both inside-out and outside-in but it’s a question of sequence. If you start with the details and ignore the big picture, it may become difficult to integrate pieces into a larger whole. Start by focusing on the big picture, the what and why of each component, and you can more readily create a space for it in the code.

So much of software comes down to how you think about the domain you’re working in and how you represent it in the software you write. The domain model should be understandable to a domain expert without a computer background. If I’m writing an accounting system, the objects in my domain model will be things like Account, Asset, Balance, Checks, and other terms familiar to accountants.

Therefore, I want my domain model to be as separate from implementation details as possible. This helps provide flexibility, consistency, and understandability. It starts from the experience of using the software versus how the software was built, and continues down into every object in the system.

When we look at the world around us, we notice that everything has its own limited perspective. When we model things in the world we also want to model their limited perspective as well. This is important because what you can hide you can change later without breaking other code that depends on it. This is the way the non-software world works and it allows us to leverage concepts as well as the implementation of concepts in our code.

Encapsulation helps reduce the “ripple effect” that change can have on a system, and it can do much more than that.

If I want to know how much cash everyone in a room has on them, it’s not appropriate for me to reach into their pockets and take it. Furthermore, some people may not carry their cash in their pockets; they may keep it in a purse—or in their socks, for that matter. I don’t want to be concerned with the details of where and how each person accesses the money he has on him. Instead, I want to ask: “How much money do you have on you?” and have each person figure it out separately and give me the result.

When implementing a system with objects, put the responsibility for each object with the object itself, where it belongs, and this gives each object in the system its purpose. Every entity in the system has its own responsibilities and if those responsibilities change, it can be hidden, to a large degree, from other parts of the system, driving down the cost of making such changes.

There are many other things that can be encapsulated, including relationships, processes, varying behaviors, the number of steps in a process, the order of steps in a process, and so on. You can hide a concept behind an abstraction. You can hide the way you implement an algorithm behind a method signature. You can wrap foreign code in your own code using the Adapter Pattern or Facade Pattern. “Encapsulation is making something which is varying appear to the outside as if it is not varying,” as Scott Bain said to me.

And there are many, many ways to encapsulate: you can hide an implementation behind a method call, hide an idea or ways of doing something behind an abstraction or interface…

Every design pattern cataloged by the Gang of Four in their book Design Patterns: Elements of Reusable Object-Oriented Software [GHJV95] encapsulates something different. Seeing patterns by what they encapsulate is a very powerful way of understanding and wielding patterns to solve problems.

Patterns say, “What you don’t know can’t hurt you.”

That’s not always true in life, where sometimes the things that hurt us the most are things we didn’t know about, but in software that’s always true. If you don’t know about something you can’t couple to it. Fewer dependencies make code easier to change.

Encapsulate by policy; reveal by need.

In other words, hide as much as you can and only reveal what is required to solve the problem. For example, start by making all of your data private and then later, if you find you need to expose more, create getter/setter methods to access the data that is protected, package, or public. It is generally much easier to expose something that is hidden than to try to hide something that’s already exposed. Only expose what is required by the problem you are solving and hide everything else.

When encapsulation becomes a habit, you’ll design from the caller’s perspective and give each little bit of functionality its own method that can be called, making its parameters and what it returns explicit. This makes clear exactly what data it needs and what is expected to be returned. This not only limits system interactions to reduce side effects, it also clearly documents the code so you know exactly what is required and expected of each method.

Perhaps the most basic and fundamental form of encapsulation is hiding the implementation of a behavior behind a method’s signature. This is echoed in the advice from Design Patterns: “Program to an ‘interface,’ not an ‘implementation.’”

Only expose what is required by the problem and hide everything else. The process of creating great software is, in many ways, the process of encapsulating as much as possible. Anyone can fulfill a specification, but great programmers are also able to encapsulate their code for maximum flexibility.