×

Decorator Design Pattern with Python

Decorator Design Patterns Python Feature

Consider a scenario where we need to add a functionality to an instance of an interface. Now if we choose to modify the interface itself, we end up violating:

  • Open Closed Principle, since we are modifying our existing code.
  • And, Single Responsibility Principle because New functionality may not interface’ primary responsibility.

Here comes, The Decorator Design Pattern, which facilitates the addition of behaviours to individual objects without affecting the behaviour of other objects from the same class.

Decorators Pattern suggests we should design a Decorator(Wrapper) class that wraps the original class. Decorator Pattern is designed so that multiple decorators can be stacked on top of each other, each adding new functionality.

Real-World analogy:

When you’re cold, you wrap yourself in a sweater. If you’re still cold with a sweater, you can wear a jacket on top. If it’s raining, you can put on a raincoat. All of these garments “extend” your basic behaviour but aren’t part of you, and you can easily take off any piece of clothing whenever you don’t need it. Wearing clothes is an example of using decorators.

Decorator Pattern may seem like creating a sub-class using inheritance. But unlike inheritance here only certain objects have additional functionality, not all objects.

Example:

Imagine there is a text editor application that provides a feature that makes the text Bold. Now we want to add more features in the application such as making the text Italic.

Consider the Text class below, having a method writeBack to print the text:

class Text:
	"""Represents the Written text """

	def __init__(self, text):
		self._text = text

	def writeBack(self):
		return self._text

Our existing feature Bold to make text bold:

class Bold(Text):
	"""Exitsting feature to make Text bold"""

	def __init__(self, wrapped):
		self._wrapped = wrapped

	def writeBack(self):
		return "<b>{}</b>".format(self._wrapped.writeBack())

Methods for new features in our application:

  • Italic
class Italic(Text):
	"""Wrapper to make Text italic"""

	def __init__(self, wrapped):
		self._wrapped = wrapped

	def writeBack(self):
		return "<i>{}</i>".format(self._wrapped.writeBack())

Italic is the Decorators(wrappers) that are adding new functionalities to the existing instance. And driver code will be like this:

if __name__ == '__main__':

    text = Text("Hello World")
    textI = Italic(Text("Python"))    
    
    print("Original :", text.writeBack())
    print("Italic :", textI.writeBack())

Clearly, objects are having varying functionalities and our existing interface also remains unaltered. Generally, the Decorator Pattern is used when it is not possible to extend the behaviour of an object using the Inheritance.

Full implementation will look like this:

class Text:
	"""Represents the Written text """

	def __init__(self, text):
		self._text = text

	def writeBack(self):
		return self._text

class Italic(Text):
	"""Wrapper to make Text italic"""

	def __init__(self, wrapped):
		self._wrapped = wrapped

	def writeBack(self):
		return "<i>{}</i>".format(self._wrapped.writeBack())

class Bold(Text):
	"""Exitsting feature to make Text bold"""

	def __init__(self, wrapped):
		self._wrapped = wrapped

	def writeBack(self):
		return "<b>{}</b>".format(self._wrapped.writeBack())


if __name__ == '__main__':

    text = Text("Hello World")
    textI = Italic(Text("Python"))    
    
    print("Original :", text.writeBack())
    print("Italic :", textI.writeBack())

Output:

Original : Hello World
Italic : <i>Python</i>

Decorator Pattern is an alternative to subclassing, which refers to creating a class that inherits functionality from a parent class. As opposed to subclassing, which adds the behaviour at compile time, “Decorating” allows adding a new behaviour during runtime.

Pros and Cons of Decorator Design Pattern

Pros

  • Helps to uphold Single Responsibility Principle and Open Closed Principle.
  • Facilitates developer to add or subtract the responsibilities from an object at runtime.
  • The Decorator Pattern is an alternative to subclassing.

Cons

  • It is very hard to remove a particular wrapper from the wrappers stack.
  • A large number of code layers might make the configurations ugly.