The world of software design is filled with various architectural patterns that help developers create robust and maintainable code. One such pattern that plays a crucial role in managing object behaviour is the Proxy Design Pattern. In this blog post, we’ll delve into the fundamentals of the Proxy Design Pattern, understand its purpose, and explore real-world scenarios where it can be effectively applied.
Table of Contents
What?
The proxy design pattern is a structural design pattern that acts as a placeholder for another object to control access to it
Why?
The Proxy Design Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. In simpler terms, a proxy acts as an intermediary, managing the communication between the client and the real object.
The Proxy design pattern is useful in various scenarios where you want to control access to an object, add some additional functionality before or after accessing the real object, or delay the creation and initialization of the real object until it is actually needed. This pattern is particularly useful when you want to add an extra layer of control, such as lazy loading, access control, or logging, to an existing object without altering its code. Here are some common use cases for using the Proxy pattern:
- Lazy Loading: The Proxy can be used to defer the creation and initialization of a resource-intensive object until it is actually needed. This is known as lazy loading and can help improve the performance of an application by only creating objects when they are required.
- Access Control: The Proxy can control access to the real object by checking permissions or conditions before allowing the request to reach the real object. This is useful for implementing security mechanisms and access control policies.
- Logging and Auditing: The Proxy can add logging or auditing functionality to the operations of the real object without modifying its code. This is useful for tracking and analyzing the behavior of the system.
- Caching: The Proxy can cache the results of expensive operations performed by the real object, allowing subsequent requests for the same operation to be served from the cache instead of re-executing the operation.
- Virtual Proxy: The Proxy can act as a stand-in for an expensive-to-create object. Instead of creating the real object, the Proxy provides a simplified version or a placeholder until the real object is actually needed.
- Remote Proxy: The Proxy can act as a local representative for an object that resides in a different address space, such as a remote server. The Proxy can handle the communication details and provide a local interface to the client.
The Proxy pattern is a versatile pattern that can be applied in various situations to add an additional layer of control, optimization, or abstraction over access to a real object. It promotes separation of concerns and helps in achieving a more modular and maintainable design
Code
from abc import ABC, abstractmethod
# Interface for the Door and SecurityProxy
class Door(ABC):
@abstractmethod
def open(self):
pass
@abstractmethod
def close(self):
pass
# RealDoor class representing the actual door
class RealDoor(Door):
def open(self):
print("Opening the door")
def close(self):
print("Closing the door")
# SecurityProxy class acting as a protection proxy to control access
class SecurityProxy(Door):
def __init__(self, real_door, is_authorized):
self.real_door = real_door
self.is_authorized = is_authorized
def open(self):
if self.is_authorized:
print("Security check passed. Opening the door.")
self.real_door.open()
else:
print("Access denied. Security check failed.")
def close(self):
self.real_door.close()
# Client code using the SecurityProxy
if __name__ == "__main__":
# Creating a real door
real_door = RealDoor()
# Creating a security proxy for the door with authorized access
authorized_proxy = SecurityProxy(real_door, is_authorized=True)
# Opening and closing the door using the authorized proxy
authorized_proxy.open()
# Output: Security check passed. Opening the door.
authorized_proxy.close()
# Output: Closing the door
# Creating a security proxy for the door without authorized access
unauthorized_proxy = SecurityProxy(real_door, is_authorized=False)
# Attempting to open the door using the unauthorized proxy
unauthorized_proxy.open()
# Output: Access denied. Security check failed.
# Closing the door using the unauthorized proxy
unauthorized_proxy.close()
# Output: Closing the door
Output
Security check passed. Opening the door.
Opening the door
Closing the door
Access denied. Security check failed.
Closing the door
In this example, the SecurityProxy class controls access to the RealDoor by performing a security check before allowing the door to be opened. This is a basic representation of a protection proxy, where access to the real object is mediated by the proxy. Depending on the specific use case, you can extend and customize the proxy to implement more complex access control mechanisms.
Use Case of the Proxy Design Pattern
- Image Loading in Web Browsers: A proxy can be used to control the loading of high-resolution images. The proxy can load a lower-resolution version initially and then fetch the high-resolution image when needed.
- Access Control in a File System: A protection proxy can restrict access to certain files based on user permissions. It can also log access requests for auditing purposes.
- Database Connection Pooling: Proxy objects can be used to manage a pool of database connections, allowing for efficient reuse and control over the number of connections created.
- Logging and Auditing: A proxy can log method calls and their parameters, providing valuable insights into the behaviour of the real object.
Conclusion
The Proxy Design Pattern is a powerful tool in the arsenal of software designers, offering a flexible way to control and extend the behaviour of objects. Whether it’s optimizing performance, adding security checks, or managing remote communication, proxies provide a clean and modular solution. By understanding the different types of proxies and their real-world applications, developers can leverage this pattern to create more scalable and maintainable systems. So, the next time you encounter a situation where you need to control access or add an extra layer to an object, consider reaching for the proxy pattern to unlock its potential in your software design journey.
Resources
For further exploration, make sure to check out these helpful resources: