Decorator Design Pattern in Python
This story is part of the “Design Patterns” series. You can find the other stories of this series here:
You can also find all the code used through this series on GitHub.
The Decorator pattern is a design pattern that allows you to add new functionality to an existing object without modifying its structure.
It does this by wrapping the object in a new object that contains the additional functionality. This new object is called a decorator.
Problems the Decorator Pattern can Solve
Imagine that you are building a web application that allows users to sign up, log in, and view their account information. You have created a class called User to represent a user of the application, and this class has several methods for performing actions such as signup, login, and view_account_info.
Now, you want to add the ability for users to request to reset their passwords. To do this, you could add a method called request_password_reset to the User class. However, this method should only be available to users who are logged in, so you add a check at the beginning of the method to see if the user is logged in.
class User:
def __init__(self, username, password):
self.username = username
self.password = password
self.logged_in = False
def signup(self):
# code to sign up a new user
pass
def login(self, password):
if password == self.password:
self.logged_in = True
return True
return False
def view_account_info(self):
if self.logged_in:
# code to view the user's account information
pass
else:
print("You must be logged in to view your account information.")
def request_password_reset(self):
if self.logged_in:
# code to send a password reset email
pass
else:
print("You must be logged in to request a password reset.")This solution works, but it is a bad design for a few reasons. First, the request_password_reset method is tightly coupled to the login method - it relies on the login method being called at some point before it is called. This makes it difficult to reuse the request_password_reset method in other contexts, since it may not always be used in a context where the user is logged in.
Second, the request_password_reset method is not very modular. If you want to add more checks or conditions to the method (for example, to only allow password reset requests if the user has verified their email address), you would have to modify the method itself. This makes the code harder to maintain, since you have to remember to add these checks every time you make changes to the method.
Solution
The Decorator pattern can solve these problems by allowing you to add additional behavior to the request_password_reset method without modifying the method itself. This makes the code more modular and easier to maintain, and it also makes it easier to reuse the request_password_reset method in other contexts.
class User:
def __init__(self, is_logged_in=False):
self.is_logged_in = is_logged_in
def login(self):
self.is_logged_in = True
def logout(self):
self.is_logged_in = False
def view_account_info(self):
print("Displaying account info.")
class PasswordResetRequest:
def __init__(self, user):
self.user = user
def request_password_reset(self):
print("Sending password reset email.")
class PasswordResetRequestWithLoginRequired(PasswordResetRequest):
def request_password_reset(self):
if self.user.is_logged_in:
super().request_password_reset()
else:
print("Error: Cannot request password reset if not logged in.")
user = User()
reset_request = PasswordResetRequest(user)
reset_request_with_login_required = PasswordResetRequestWithLoginRequired(user)
# This should fail because the user is not logged in
reset_request_with_login_required.request_password_reset()
# Now we'll log the user in and try again
user.login()
reset_request_with_login_required.request_password_reset()In this example, the PasswordResetRequest class represents the base behavior for requesting a password reset.
The PasswordResetRequestWithLoginRequired class is a decorator that adds the additional behavior of checking if the user is logged in before allowing the password reset request to be made.
This allows you to add the login requirement without modifying the PasswordResetRequest class itself, making the code more modular and easier to maintain.
Applications
- Adding new behavior to individual objects at runtime
- Decorating objects without the need to subclass
- Composing objects to create more complex behaviors
Advantages
- Allows for more flexibility than static inheritance
- Avoids the need to use a large number of subclasses to achieve the desired behavior
- Keeps the original object intact and separate from the new behavior
Disadvantages
- Can result in a lot of small, single-purpose objects, which can make the code harder to read and understand
- The decorators can become complex if many different behaviors are added
- Can be difficult to understand the overall behavior of an object when it has been decorated with many decorators
Final Note
The Decorator pattern is a powerful tool for adding new behavior to objects at runtime. It is especially useful in Python, where the use of inheritance and static typing is not as common as in some other programming languages. In Python, it is often easier to use the Decorator pattern to add new behavior to an object than to use inheritance or other forms of static modification.
The Decorator pattern is natively implemented in Python with @function. I have not talked about it because this article is more about understanding the concept of the Decorator than the implementation, but I’ll probably talk about it later.
To explore the other stories of this story, click below!
To explore more of my Python stories, click here! You can also access all my content by checking this page.
If you want to be notified every time I publish a new story, subscribe to me via email by clicking here!
If you’re not subscribed to medium yet and wish to support me or get access to all my stories, you can use my link:






