The Proxy Pattern in Python
Control access to objects while adding functionality or making them virtual
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 Proxy pattern is a design pattern used to provide a surrogate or placeholder object which references an underlying object. The proxy controls access to the underlying object and can add additional functionality or behavior.
Problems the Proxy Pattern can Solve
Imagine that we’re building a system that allows users to upload and download files. The system has a File class that represents a file on the server, and a User class that represents a user of the system.
class File:
def __init__(self, file_path):
self.file_path = file_path
def download(self):
# Code to download the file
print(f'Downloading {self.file_path}')
class User:
def __init__(self, name):
self.name = name
def download_file(self, file):
if self.name != 'admin':
print('You are not authorized to download this file')
else:
file.download()
if __name__ == "__main__":
file = File('important_file.txt')
user = User('guest')
user.download_file(file)
user = User('admin')
user.download_file(file)In this example, the User class is responsible for checking if the user is authorized to download a file, and the File class is responsible for downloading the file. This design violates the Single Responsibility Principle because the File class is responsible for both representing a file and providing the functionality to download the file. The User class is also responsible for checking the authorization of the user, which could be a more complex process.
Also, if there are any other security or access control checks that needs to be done before downloading the file, it would need to be implemented in the User class, making it more complex and harder to maintain.
Solution
This problem can be solved by introducing a FileProxy class that acts as a proxy for the File class and controls access to the underlying file. This way we can separate the responsibilities of checking the authorization, and the download process.
class File:
def __init__(self, file_path):
self.file_path = file_path
def download(self):
print(f'Downloading {self.file_path}')
class FileProxy:
def __init__(self, file_path, user):
self.file = File(file_path)
self.user = user
def download(self):
if self.user != 'admin':
print('You are not authorized to download this file')
else:
self.file.download()
class User:
def __init__(self, name):
self.name = name
def download_file(self, file):
file.download()
if __name__ == "__main__":
user = User('guest')
file = FileProxy('important_file.txt', user.name)
user.download_file(file)
user = User('admin')
file = FileProxy('important_file.txt', user.name)
user.download_file(file)This design separates the responsibilities of checking the authorization and the download process, making the code more maintainable and extensible. The File class only needs to be concerned with providing the functionality to download the file and the FileProxy class is responsible for controlling access to the file.
Also, the FileProxy class can be easily extended to add other security checks.
Applications
- Controlling access to an object or a resource
- Adding functionality to an object without modifying the object itself
- Creating a more lightweight version of an object
- Creating a virtual representation of an object
Advantages
- Separates the responsibilities of controlling access to an object and providing the functionality of the object
- Makes code more maintainable and extensible by reducing the complexity of the objects being used
- Allows for the addition of functionality to an object without modifying the object itself
Disadvantages
I don’t really see disadvantages to using the Proxy pattern, except the fact that it can increase the complexity by adding an abstraction layer, and by adding more classes and interfaces.
Final Note
The Proxy pattern is a bit hard to understand at first, but it can be useful in various situations, especially when you want to separate the responsibilities of controlling access to an object and providing the functionality of the object, making the code more maintainable and extensible.
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:
