Adapter design pattern in python

Definition: The adapter design pattern is the most efficient way to make objects compatible with each other. Through this, we can convert one interface class to another per the client's requirements. The adapter of one special object will convert the interface of one object into the desired format so that another object can understand and perform necessary actions on it. Here adapter wrapper is used to hide the complexity behind the conversion that is happening inside. Adapter objects not only help in conversion but can also help in the collaboration of objects.

This is one of the variations of structural design patterns. There is some similarity between the bridge design pattern and adapter design pattern, both consists of the interface. Still, there are some major differences in the working of the interface. Adapted design patterns have the capability of containing two independent interfaces which cannot be done in the bridge method. The main objective behind this is to make objects compatible with each other.

Suppose you have a laptop that does not have LAN(local area network) port, but it has a USB port. In your house, there is no router. All you have is a LAN cable, so you must need a LAN port to connect to the internet, as there is no LAN port on your laptop how will you use the internet? Think once that you have a USB port in your laptop, so if you will connect LAN to a USB adapter then you can easily connect to the internet. Here LAN to USB connector works as an adapter and serves the client. This is how the adapter design pattern works. We are making the USB and LAN ports compatible and doing our required tasks without interruption.

Problem statement :

Suppose you are making a cryptocurrency monitoring app. It took a lot of effort and hours of coding to complete this project. The app downloads the cryptocurrency prices from various sources in XML format and then displays all the information to the user. An API fetches the data of different cryptocurrencies and is configured to return data in XML format. After some months, the client wants to add a machine-learning algorithm to the app. The client wants the app to return only the important data that the user asks for, so now you have to integrate the machine learning algorithm into the app. Still, there is a problem, machine learning accepts data only in JSON format, but the app returns data in XML format. So what will you do? Will you change all the configurations of the app and API? But this is not an efficient solution because you would have to change the internal configurations of code and many more. Implementing this solution approach is impossible.

Solution statement :

Adapter design pattern in python

The most reliable solution is the creation of an adapter that converts the data into the desired format. To solve this problem, we can easily create one XML to JSON converter adapter, which will be used to perform and implement the machine learning algorithm. The app will return the data in XML format, then it will be converted to JSON through the adapter, then the machine learning algorithm will perform the operations, and the desired data will be delivered to the client.

The most common problem we face while traveling the different countries is that electric sockets are different in different countries, so you cannot use the Indian charger directly in the USA to charge your mobile phone. The same problem will be faced when you travel to another county, so here we use a power plug adapter, and then we can charge our phone with the same charger even in different countries. So, from the above discussion, you must have some ideas regarding the importance of an adapter. Even two objects can become compatible by connecting an adapter, reducing the overall problem. So let's see how the adapter design pattern helps us in python.

Example 1.py code :

# newbook 
# oldboook 
# book


class newbook:
  """Class for newbook"""
 def __init__(self):
    self.name = "newbook"
def three_pages(self):
    return "three_pages"
class oldbook:
  """Class for oldbook"""
  def __init__(self):
    self.name = "oldbook"
  def Eight_pages(self):
    return "Eight_pages "
class book:
  """Class for book"""
  def __init__(self):
    self.name = "book"
  def Four_pages(self):
    return "Four_pages"
class Adapter:
  """
  By replacing methods, it  Adapts an object.
  Usage:
 Newbook = newbook()
objects.append(Adapter(Newbook, pages = Newbook.three_pages))
  """
def __init__(self, obj, **adapted_methods):
    """ in the object's dict We set the adapted methods"""
    self.obj = obj
    self.__dict__.update(adapted_methods)
 def __getattr__(self, attr):
    """this is for non adapted objects where all non adpated objects are passed to it """
    return getattr(self.obj, attr)
 def original_dict(self):
    """the  original object dict is printed"""
    return self.obj.__dict__
""" main method """
if __name__ == "__main__":
 """list for storing the objects"""
  objects = []
Newbook = newbook()
objects.append(Adapter(Newbook, pages = Newbook.three_pages))
Oldbook = oldbook()
objects.append(Adapter(Oldbook, pages = Oldbook.Eight_pages))


Book = book()
objects.append(Adapter(Book, pages = Book.Four_pages))


for obj in objects:
                    print("the {0} is consists of  {1} book of paper".format(obj.name, obj.pages()))

Output.txt :

the new book is consists of  three_pages book of paper
the old book is consists of  Eight_pages  book of paper
the book is consists of  Four_pages book of paper

Explanation :

You can see there are three classes new book, old book, and book with pages attribute.

Code for creation of class:

class newbook:
  """Class for newbook"""
 def __init__(self):
    self.name = "newbook"
def three_pages(self):
    return "three_pages"

This is the creation of the class. Firstly we have declared the name of the class. In the self-name, we have given the name to the object. Lastly, we created one attribute three_pages, and returned a string within this object. This was the basic structure of the class. The remaining two classes, old and new books, are created in the same fashion.

Then for the adapter creation, we have created two objects first one will store only the adapter, and the second one will store non-adapter objects.

Code for implementing adapter method :

def __init__(self, obj, **adapted_methods):
    """ in the object's dict We set the adapted methods"""
    self.obj = obj
    self.__dict__.update(adapted_methods)

Here we have created a dictionary that will store the adapter methods. Firstly we store the object. Then we create an update method, the update method will create an adapter if the client wants. We store all the non-adapter objects in _getattr objects. We have created an adapter for all the available attributes so we can access any of the objects.

Code for Calling adapter

Book = book()
objects.append(Adapter(Book, pages = Book.Four_pages))

 We call the adapter, append it with the existing object, and then print it. This is how we create and call adapters and access the objects. This simplifies our work because we only have to define any attribute once, and then we can append it anywhere in any object.

Advantages of adapter method :

  • The open/close principle is not violated by this method.
    [note-the open/close principle is one of the most important principles of object-oriented programming. It says software entities should be open for extension but must be closed for modification. Simply, it means adding new functionality to existing code without interrupting or changing the existing code and functionality.
  • We do not have to use multiple interfaces. Hence it makes classes simple and easy to code.
  • The adapter design pattern makes the code more reusable.
  • The code becomes more flexible by using this method. We can easily adapt any attribute to any object without changing the core implementation of class and object.
  • Here the primary logic of the client is totally separated from the core classes. It follows the principle of single responsibility.

Disadvantages of adapter method :

  • Sometimes we have to add many new classes, which might complicate the existing code.
  • Suppose we are working on a very small project. In that case, we should avoid this method, and we can follow the conventional method and add attributes manually to avoid creating extra classes.