DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป is a community of 966,904 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Encapsulate and abstract for future-proof software
Ricardo Costeira
Ricardo Costeira

Posted on

Encapsulate and abstract for future-proof software

Change will always affect your software. No matter the domain, the uses cases, the developers, or even the users. Change is the one constant in software development.

This is one of the first topics addressed by the authors of the renowned Head First Design Patterns. They approach it as one reason for the importance of design patterns.

โ€œNo matter how well you design an application, over time an application must grow and change or it will die.โ€œ
โ€” Head First Design Patterns, Chapter 1, page 8

Along with design patterns, the authors also introduce a bundle of design principles. While the patterns are outside the scope of this article, I want to focus on the first two principles:

  • Encapsulate what varies.
  • Program to interfaces, not implementations.

The first principle is the basis for all design patterns, and most of them also make use of the second one. The first one dictates that if you have code that keeps changing, pull it out and isolate it. The second principle complements this through the use of interfaces.

Now, a word of caution. As Vasiliy Zukanov explained in this comment, this โ€œinterfaceโ€ does not refer to the interface construct seen in some OOP languages. Well, it can refer to it, but it has a broader meaning. Here, โ€œinterfaceโ€ refers to a componentโ€™s external point of interaction. It is what other components can use to interact with the specific component. So, this โ€œinterfaceโ€ can be an interface, an abstract class, a normal class or even a function. It can be anything as long as it serves as a communication point with the component. With it, we need not know the inner details of the component. It lets us abstract from the componentโ€™s implementation. So, whenever thereโ€™s a change, you only need to refactor the corresponding code. The outside code will never even notice it. The purpose of the principle is indeed to focus on what the code does, and not how it does it.

A ticking time bomb: Android Libraries

The Android open source community is awesome. No matter the complexity of what you need, a library implementing it is likely to exist already. This not only makes our jobs easier but also lets us focus on the true business logic problems.

Yet, things change (I know). Libraries become obsolete. Sometimes, new versions introduce breaking changes. Requirements change, and we no longer need a library. External changes force us to change our code. Weโ€™re left with a huge codebase full of deprecated dependencies or code built around them. This is where the design principles mentioned above come in handy.

Suppose that you need to store/retrieve a Configuration object on/from disk in JSON format. You have experience with Gson from previous projects, so you use it. You defined Configuration as:

You first start by creating an abstraction for Gson. Here, a simple class will do (unless youโ€™re using Clean Architecture: in that case, you would have this class implement an interface):

Then, you use it along with the rest of your product:

Time goes by, and your abstraction gets sprinkled throughout your code. One day, you come across this hip library called Moshi, that also deals with json parsing. Moshi seems to be faster, more flexible, and works like a charm when used together with Retrofit. You got to use it.

Luckily, you saw this coming. You use Gson everywhere in your code. But since you have it encapsulated, you can swap it with Moshi almost for free!

Simply replace Gson with Moshi:

And you're done โ€Š- โ€Šall the code that used Gson now uses Moshi. Just by changing this class.
You can later change libraries again. You can even ditch json and use something else. As long as you create the proper abstraction (which is actually the hard part), you're good to go. Your code is now robust and flexible, and your future self will be proud.

Note that the codebase is further improved by injecting the dependencies. Even if you donโ€™t use Dagger or any other frameworkโ€Šโ€”โ€Šthe dependency injection itself is what matters. This way, you keep your classes decoupled and set yourself up for easy testing. Here, if you inject a mock or fake storage handler, you can test MagicBusiness in isolation.

Conclusion

โ€œAbstractions Live Longer than Detailsโ€œ
โ€” The Pragmatic Programmer, chapter 7, page 209

I showed you an example of how you can create boundaries around your code. These boundaries protect your code from external dependencies. Still, it goes much deeper than this. Recipes like design patterns or architectural patterns such as Clean Architecture are great. Theyโ€™re battle tested, and their usefulness is more than proven. Using these design principles is one reason for their greatness. You can (and should!) apply these design principles even if you donโ€™t use external code. Use them with caution, though. We know that design principle abuse increases code complexity. Itโ€™s a commitment you must consider, and balance with care.


If you got this far, thank you very, very much :) if you want to keep in touch, you can find me on Twitter or check out my GitHub. This article was also published on Medium.

Top comments (0)

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. ๐Ÿ›