Decorator design pattern in python

Definition: This is one of the variations of the structural design pattern, which allows adding new functionality to existing classes without making changes to the existing structure, which refers to adding more responsibility to any object. This operation is performed by wrapping the objects. The decorator design pattern in python is also known as a wrapper.

Here inheritance is used to add new functionality. The functionalities are added dynamically during runtime without affecting the existing functionality. A decorator object is used to add functionality to other objects. Interfaces or abstract class is used with the composition method to implement the wrapper. Functionality is decided into unique classes, and the single responsibility principle is applied. It is similar to the responsibility pattern of the chain. It provides greater flexibility than static inheritance. It enhances the extensibility of the object because coding new classes make changes. It simplifies the coding by allowing you to develop a series of functionality from targeted classes instead of coding all the behaviour into the object.

When to use decorator design pattern :

  • Decorator design patterns can be used when you want to add functionality transparently and dynamically without any interruption in the functionalities of existing objects.
  • When the added functionalities might need to be changed, we can use the decorator design pattern.
  • When you no longer want to add functionality by creating subclasses.

Problem statement :

Decorator Design Pattern In Python

Suppose you have a rectangle object that is used to draw a rectangle. There is a Draw method inside this. You just need to call the Draw, and then it will draw a rectangle for you. Now you want to add colours to this shape, but you no longer want to change the basic structure of the code or the object. How can this be done? In the conventional method, we must change its basic structure if we want to add anything new to any object and class. Then we will add those functionalities, but in this decorator design pattern, we are not allowed to make any changes to the basic structure of the code.

Solution statement :

Decorator Design Pattern In Python

We can overcome this problem by using a decorator design pattern. We will introduce the decorator object, and it will wrap up our existing object, as shown in the figure below. The working of the decorator object is simple. Firstly you can see it has both draw and colour features, so when we call it, it will draw the rectangle, and then it will set the colour of it. This is how we are implementing the decorator design pattern.

You might think we could have added colour to the rectangle shape by modifying the existing structure. Surely you can do so, but this is applicable only for the short-run, not for the long run because here you only have one class with one object so you can follow the conventional method. It produces no complexity but suppose if you have one thousand classes with a few hundred objects, can you follow the conventional method here to add the functionalities? Obviously, no, it is just impossible to implement. So here, we have to use the decorator pattern to perform the necessary task. It is less time-consuming, adds nearly no complexity to our existing code, and boosts software performance.

Real-world analogy:

normally, what do we wear? If it is summer, we wear light clothes, and if it starts raining, we wear a raincoat, but if we are on the beach, we wear minimum clothes. In winter we wear sweaters after wearing normal cloth, but even after wearing sweaters if you are not feeling warm you can wear a shawl on the top, so all of these are not part of you, but they can extend if there is a need, you can use and not use them according to the situation. This happens in the decorator design pattern, which allows adding new functionality to existing classes without changing the structure.

Implementation of decorator design pattern :

Example.py code: 

class notification():
    """
    Interface for notification creation
    """
    def create_notification(self):
        pass


class Concretenotification(notification):
    """
    Implements interface notification
    """
    _notification = "showing all notification on ...\n"
    def create_notification(self):
        return self._notification


class notificationDecorator(notification):
    """
    base class for decorators
    """
    def __init__(self, decorated_notification):
        # this is for "has-a" relationship with Story class
        self.decorated_notification = decorated_notification
    def create_notification(self):
        return self.decorated_notification.create_notification()


class Facebook(notificationDecorator):
   _platform = "Facebook"
   def __init__(self, decorated_notification):
        super().__init__(decorated_notification)
   def create_notification(self):
        return f"{self.decorated_notification.create_notification()}- {self._platform}\n"
class Instagram(notificationDecorator):
  _platform = "Instagram"
  def __init__(self, decorated_notification):
        super().__init__(decorated_notification)
  def create_notification(self):
        return f"{self.decorated_notification.create_notification()}- {self._platform}\n"
class Whatsapp(notificationDecorator):
  _platform = "Whatsapp"
  def __init__(self, decorated_notification):
        super().__init__(decorated_notification)
  def create_notification(self):
        return f"{self.decorated_notification.create_notification()}- {self._platform}\n"


class Linkedin(notificationDecorator):
  _platform = "Linkedin"
  def __init__(self, decorated_notification):
        super().__init__(decorated_notification)
  def create_notification(self):
        return f"{self.decorated_notification.create_notification()}- {self._platform}\n"
class Snapchat(notificationDecorator):
    _platform = "Snapchat"
    def __init__(self, decorated_notification):
        super().__init__(decorated_notification)
    def create_notification(self):
        return f"{self.decorated_notification.create_notification()}- {self._platform}\n"
def show_off():
    my_notification = Concretenotification()
    my_notification = Facebook(Instagram(Whatsapp(Linkedin(Snapchat(my_notification)))))
    print("Activating notitication  mode. Let'see it on the available platforms!")
    print(my_notification.create_notification())


def professional():
    my_notification = Concretenotification()
    my_notification = Linkedin(my_notification)
    print("Let's see what notification you have recieved ")
    print(my_notification.create_notification())
if __name__ == '__main__':
    show_off() 
    professional()

Output.txt:

Activating notification mode. Let's do it on the available platforms!
showing all notification on ...
- Snapchat
- Linkedin
- Whatsapp
- Instagram
- Facebook


Let's see what notification you have received 
showing all notifications on ...
- Linkedin

Explanation :

The code above is an example of a decorator design pattern. We have created classes named Whatsapp, Facebook, Instagram and LinkedIn, and then we have added the functionality of notification by decorator design pattern. We have wrapped the classes inside the notification decorator class, and then the whole thing is implemented. Let's understand the code.

Creating the decorator class:

class notificationDecorator(notification):
    """
    base class for decorators
    """
    def __init__(self, decorated_notification):
        # this is for "has-a" relationship with Story class
        self.decorated_notification = decorated_notification
    def create_notification(self):
        return self.decorated_notification.create_notification()

We have created methods for creating notifications. This is the main decorator class which will be wrapping the other classes for providing the notification facility. Whenever any classes are attached to this doctor, they will be facilitated with all the existing methods of it. Then they can access the create notification class and create a notification for them.

Creating classes :

class Facebook(notificationDecorator):
   _platform = "Facebook"
   def __init__(self, decorated_notification):
        super().__init__(decorated_notification)
   def create_notification(self):
        return f"{self.decorated_notification.create_notification()}- {self._platform}\n"

This is the Facebook class. You can see we have passed the decorator here. We have defined the platform as Facebook. Then we have called the super method(it is used for the constructor), which will let the Facebook class access the create notification method. Then we added the notification functionality to it.

The pattern of code is the same for all other classes. Then we have created a show_off method which will help to print the values, so after adding all classes and decorator, we create show_off and professional class./CO

show_off() 
professional()

These will print the existing values, which will help us to understand whether the notification feature is added successfully and perfectly or not.

Advantages :

  • It follows the single responsibility feature, which simplifies the code.
  • It follows the open-close principle.
  • We do not need to worry if we have not added the responsibilities in compile time. We can easily add the responsibilities in the run time. It makes our job easy and reliable.
  • It is one of the best alternatives for subclassing. The working of subclassing is the same, but the main drawback is that we must add everything during compile time. Adding functionalities during runtime is impossible so we can use the decorator design pattern as a better alternative to the subclassing.
  • The instances of the original class remain unchanged in the decorator design pattern, but in the case of subclassing, the original instances will change.

Disadvantages of decorator design pattern :

  • If the wrapper is attached, removing it is a very tough job. It is a complex process.
  • Decorators are implemented in multiple layers, so keeping the updates of the decorator in mind is a difficult task; you cannot track them efficiently due to multi-layering.
  • The configuration and structure become weird by using decorator design patterns.