×

Adapter Design Pattern with Python

Adapter Design Patterns Python Feature

The Adapter design pattern is a structural design pattern, which suggests having an interface acting as a bridge between two incompatible interfaces to collaborate. This pattern combines the functionalities of two independent interfaces.

Let’s first of all, know What is an Adapter?

  • A real-life example could be the case of a card reader, which acts as an adapter between a memory card and a laptop.
  • In techincal terms, Adapter: A construct that adapts an existing interface X to conform to the required interface Y.

The design pattern got its name because its purpose is the same – adapting one input to a different predetermined output.

This pattern involves the creation of an interface, which will be responsible to join functionalities of incompatible interfaces. The adapter method saves the developer from redesigning the entire software from scratch if the software involves two incompatible entities who need to communicate.

Let’s say we were working with XML data sets, in an application now we want to add some sort of machine learning algorithm in our application, but algorithms work beautifully on JSON data sets. And, modules of the same application works seamlessly with XML.

Here rather than restructuring our whole application for JSON or XML, it’s better we introduce some method(a common ground, i.e Adapter) between them to function together with incompatible data sets.

There are some terminologies, worth knowing when working with Adapter Design Patterns.

  • Adaptee: The existing class or interface.
  • Adapter: The class in charge to cater the client’s needs.
  • Client: The class who wishes to communicate with adaptee, but incompatible.

A python interface for reference, representing Client, Adaptee and Adapter, below:

The Client class, which will have some requirements(i.e. request)to communicate with an existing interface.

from abc import abstractmethod

#The client
class Client:
    def __init__(self) -> None:
        self._adaptee = Adaptee()

    @abstractmethod
    def request(self):
        pass

Adaptee the existing interface, which will provide some functionality(i.e. specific_request ) to the client.

#The existing interface
class Adaptee:  

    def specific_request(self):
        pass

Adapter common ground between the Client and the Adaptee.

#Adapter the interface of Adaptee to the Target(i.e. client) interface.
class Adapter(Client):    

    def request(self):
        self._adaptee.specific_request()

The whole procedure is, Client has a method request() which represents the need to communicate with Adaptee, for Adaptee‘s functionality specific_request(). Since both will be incompatible, hence Adapter will provide specific_request() to Client on behalf of Adaptee. Full code will look like this:

from abc import abstractmethod

#The client - New interface
class Client:
    def __init__(self) -> None:
        self._adaptee = Adaptee()

    @abstractmethod
    def request(self):
        pass

#The existing interface
class Adaptee:  
    def specific_request(self):
        pass

#Adapter the interface of Adaptee to the Target(i.e. client) interface.
class Adapter(Client):    
    def request(self):
        self._adaptee.specific_request()

#The diver code
def main():
    adapter = Adapter()
    adapter.request()


if __name__ == "__main__":
    main()

One point worth noting is: There can be a chain of Adapters due to incompatibility between several entities.

Pros and Cons of Adapter Design Pattern

Pros

  • Upholds the principle of Single responsibility because here we can separate the concrete code from the primary logic of the client and put it into the Adapter interface.
  • The Adapter Method helps in achieving the flexibility and reusability of the code.
  • Since, we keep our Client and Adaptee interfaces unchanged, hence, Adapter Design Pattern uphold the Open-Closed Principle.

Cons

  • Since we are introducing the Adapter interface(s), code complexity may increase.