This time I`ll remind you about a pretty helpful pattern named adapter. It helps to unify different contracts into one and makes code easier to maintain and read
Introduction
The adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
Let`s examine a problem when we face a situation where systems with incompatible interfaces should collaborate. I will highlight the most trivial case from the top of my mind. We have different sources of information, and we have to retrieve or put some data from/to all of them. But as it always happens, each external source has a different contract.
If we were terrible developers, we would probably write something like the following code example:
In the example above, we have two different sources of analytic data and a single method in AnalyticRetriever which hides from external client complexity of calling proper analytic source.
Problems
With the new analytic source, we have to make changes in AnalyticRetriever. An additional case in switch breaks the open-close principle. A lot of logic must be covered in unit tests for AnalyticRetriever. With new analytic sources, the number of tests will increase drastically. We can say that in this situation, GetItemsAsync is a GOD method that has a lot of knowledge about external sources.
The adapter is a silver bullet for such a case
As the adapter description states, its main goal is a collaboration between incompatible interfaces. To achieve this, we need to refactor the code above:
As you can see in the adapter example, which is much better, and here is why a new interface was introduced and two different implementations were created for each source, respectively.
Each implementation hides a switch case branch. Such an object approach allows making AnalyticsRetriever so flexible that we don`t need to touch it in case of a new source. New adapter implementation will contain all required logic for a new source.
Also, we have a cleaner unit tests coverage of all business logic for retrieving separated in adapter classes, and it`s straightforward how to cover it with unit tests.
We made unification with the new interface. This is the central part of the adapter pattern that we need to introduce an abstraction that will hide all differences from third-party interfaces.
Conclusion
The adapter gives us an object-oriented way to handle program flow instead of a simple form with ifs or switches. It covers specific cases and makes our code more readable and maintainable.