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:
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
specific_request(). Since both will be incompatible, hence
Adapter will provide
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
- 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.
- Since we are introducing the Adapter interface(s), code complexity may increase.