Leaving Your Options Open With Adapters

Very often, when developing an app, people need to work with code/libraries/components written by someone else. This need introduces the issue of compatibility between existing code and the third-party code we want to “communicate” with.

A few examples of when this situation can come up include:

  • we are required to avoid modifications of the existing application code when introducing a third-party component to our app,
  • we do not have access to the third-party library’s code (we can only communicate through its public API),
  • we do have have access to the third-party library’s code, but we don’t want to modify it,
  • we want to experiment with more than one libraries that all use different interfaces,
  • we have to replace a third-party component with a new one (because the existing one broke after an OS update for example).

In our example, we will be swapping a third-party menu component for a new one that fits our application’s needs better.

 

Example

In our code, we have included a protocol/interface that expects the classes conforming to it to implement the showMenu and hideMenu methods for displaying and hiding the menu respectively. The problem is that the third-party menu component that we want to use, includes two methods named presentMenu and dismissMenu to achieve the same functionality.

adapter

We are in a situation where two classes with incompatible interfaces need to communicate/work together.

To make the above situation work, we would have to change either our existing code or the code on the third-party library’s side (assuming the source code is actually available to us to tamper with). Both solutions violate the [PDF Warning] Open Closed Principle [/PDF Warning] and could lead to maintenance problems. What if we end up using another library with a completely different interface? We would have to resort to modifications, risking breaking something in our application.

But for the two classes to work together, we need to establish some kind of communication between them.

 

Adapter To The Rescue

We will use the Adapter Design Pattern which according to the Gang Of Four allows us to “Convert the interface of a class into another interface the client expects. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”

adapter_2

The code on the client’s side will simply call the respective methods of the Adapter object:

class ExistingCode{
    
    var menuAdapter: Menu
    
    //....
}
func showMenu()
{
     self.menu.showMenu()
}
func hideMenu()
{
     self.menu.hideMenu()
}

The Adapter, in turn, will simply call the third-party’s methods like this:

class ThirdPartyMenuAdapter: Menu
{
    var thirPartyMenu: CoolMenuWeFoundOnGitHub
}
func showMenu()
{
     self.thirdPartyMenu.presentMenu()
}
func hideMenu()
{
    self.thirdPartyMenu.dismissMenu()
}

The two incompatible classes now remain decoupled thanks to the Adapter that essentially plays the role of a middleman between them.

Also note that if we choose to use a different library, all we have to do is write an adapter for the new library while leaving the application code untouched. We can also use the Strategy Design Pattern to swap Adapters at runtime.

 

Things To Note

  • In this example, the Adapter conformed to a protocol/interface . This provides the flexibility to use as many adapters/libraries as we want and switch between them at runtime. This becomes extremely helpful when we want to test various third-party components to find the one that best suits our needs.
  • It is really important that the interfaces used in an application are designed based on the app’s point of view. Very often, developers let third party code take control of their application by letting dependencies sneak into the core of the app. When they decide the third party component needs to be changed, it is usually too late and they need to proceed to major refactoring. Inversion of control is an important topic that will get its own separate article soon.
  • Adapters are everywhere. Many (most?) developers spend their careers writing adapters to provide communication between incompatible interfaces and applications. In enterprise environments especially, mergers and acquisitions lead to the need for integration between software varying in any possible way imaginable.
  • Adapters should not be used in greenfield projects. There, other designs and architectures should be considered. Software adapters, like their material world counterparts, are meant to be used to integrate already implemented solutions that cannot change their interfaces easily (if at all).

Further Reading

Books:

Clean Code: A Handbook of Agile Software Craftsmanship  (One of my favourite engineering books)

Refactoring: Improving the Design of Existing Code (Martin Fowler’s classic)

Working Effectively with Legacy Code (Maintain your sanity when working with code other people wrote, possibly decades ago)

Design Patterns: Elements of Reusable Object-Oriented Software (The Gang Of Four Book)

Head First Design Patterns (In my experience, the best introductory book on Design Patterns)