The Proxy Design Pattern: Mastering Controlled Access to Objects
Software development practices entail a myriad of challenges, one of which is the efficient management of resources. As developers, we often find ourselves in a position where we need to control access to an object, perhaps due to the cost of creating and maintaining the object, or because of access restrictions. In this scenario, the Proxy Design Pattern comes to the rescue. Let’s delve into the world of the Proxy Design Pattern, learn how to use it in Python, discover its pros and cons, and look at its usage in the Python standard library.
The article is part of my series on Software Design Patterns. For a more general introduction to design patterns, refer to this overview article, which is an excellent starting point for exploring the fascinating world of software design patterns.
As usual, the sample code shown in the article can be found on my Github repository.

Understanding the Proxy Design Pattern
In essence, the Proxy Design Pattern involves creating a new proxy class as an interface for the actual class. This pattern is a type of structural design pattern as it’s concerned with how classes and objects can be composed to form larger structures. The Proxy Design Pattern introduces a layer of protection to the actual object from the outside world. This is particularly useful when the original object is vulnerable, or complex and heavy.

As we can see, the pattern is not that complicated:
Subject
is an interface that declares common operations forRealSubject
andProxy
.RealSubject
is a class that defines the real object that the proxy represents.Proxy
is a class that maintains a reference to aRealSubject
, and can control access to it. The proxy often does some additional operations before and after forwarding the request to theRealSubject
such as access checks and logging.- The
Client
accesses theRealSubject
via theProxy
by means of the defined interfaceSubject
.
Now, let’s translate the UML diagram for the Proxy Pattern over into actual Python code:
import logging
from abc import ABC, abstractmethod
class Subject(ABC):
"""
The Subject interface declares common operations for both
RealSubject and the Proxy.
"""
@abstractmethod
def do_action(self) -> str:
"""
The Subject interface declares a common method for both
RealSubject and the Proxy.
"""
class RealSubject(Subject):
"""
The RealSubject contains some core business logic.
"""
def do_action(self) -> str:
"""
Work done by the RealSubject.
"""
return "RealSubject: Handling request."
class Proxy(Subject):
"""
The Proxy has an interface identical to the RealSubject.
"""
def __init__(self, real_object: RealSubject) -> None:
"""
The Proxy maintains a reference to an object of the RealSubject class.
"""
self._real_object = real_object
def do_action(self) -> str:
"""
Work done via the Proxy.
"""
if self.check_access():
self._real_object.do_action()
self.log_access()
return "Proxy: Handling request."
def check_access(self) -> bool:
"""
Helper function to check access rights before firing a real request.
"""
logging.info("Proxy: Checking access prior to firing a real request.")
return True
def log_access(self) -> None:
"""
Helper function to log access to the real subject.
"""
logging.info(
"Proxy: Logged the time of request.",
)
def client_code(subject: Subject) -> str:
"""
The client code is supposed to work with all objects (both subjects and
proxies) via the Subject interface in order to support both real subjects
and proxies. In real life, however, clients mostly work with their real
subjects directly. In this case, to implement the pattern more easily, you
can extend your proxy from the real subject's class.
"""
return subject.do_action()
# Executing the client code with a real subject.
real_subject = RealObject()
client_code(real_subject) # --> RealSubject: Handling request.
# Executing the same client code with a proxy.
proxy = Proxy(real_subject)
client_code(proxy) # --> Proxy: Handling request.
In this example, the Client
interacts with the Subject
interface, which is common to both RealObject
and Proxy
. The RealSubject
contains the actual business logic, and the Proxy
manages access to the RealSubject
, performing additional tasks such as checking access rights before and logging after forwarding requests to the RealSubject
.
Advantages and Drawbacks
Let’s delve into the advantages and potential pitfalls associated with the application of the Proxy pattern.
Advantages
- Controlled Access: The Proxy Design Pattern is primarily lauded for the controlled access it offers. By interposing a proxy or surrogate for an actual object, the pattern permits requests to be processed or relayed as required. For instance, in the case of a large and resource-heavy object, the proxy can delay the instantiation until necessary, thus saving system resources. In some cases, it can also limit the number of instances created for a particular object, which can prove useful in the context of network connections or database access.
- Reduced Complexity: Another significant advantage of the Proxy Design Pattern is the reduction in system complexity it brings about. It achieves this by abstracting the underlying complexities of the real object. This means that the client can interact with the proxy without needing to understand the intricacies of how the actual object works, its lifecycle, or how it’s implemented. This makes the system as a whole more user-friendly and manageable, as the client needs only to interact with the straightforward interface provided by the proxy.
- Security Enhancement: The Proxy Design Pattern can add a layer of security to the real object. By interposing itself between the client and the actual object, the proxy can control and monitor the access to the real object. For instance, it can check if the client has the necessary permissions or credentials before forwarding the request to the real object. This is particularly useful in situations where the real object contains sensitive data or functionality that should only be accessible under specific conditions or by certain clients.
Drawbacks
- Code Complexity: One of the main drawbacks of the Proxy Design Pattern is that it can lead to increased code complexity. This is primarily because the pattern necessitates the introduction of additional classes, namely the Proxy class itself. Moreover, maintaining coherence between the interface of the Proxy class and the Real object class can add to the overhead, especially when the system evolves and the interface needs to change. Furthermore, the use of proxies may not be immediately obvious to other developers, making the code harder to understand and maintain.
- Response Time: Another potential drawback is the impact on response time. Since the proxy sits between the client and the actual object, all requests and responses must pass through the proxy. This added layer can cause a slight delay in the response time. Although this latency might be negligible in many scenarios, it could become significant if the proxy performs complex checks or the interactions with the real object are particularly time-sensitive. This is especially true in high-performance systems where even minor delays can be a major drawback. Consequently, the Proxy Design Pattern should be employed judiciously, taking into account the system’s performance requirements and the potential latency introduced by the proxy.
Combining Proxy with Other Patterns
Design Patterns can be combined effectively with other design patterns for more optimized solutions. We might not have explored all the patterns, but I will start to incorporate this section — just skip over the unknown patterns for now and come back later.
- The Adapter Design Pattern, for instance, can be used with the Proxy Pattern when the proxy class needs to change the interface of the real object without altering its behavior.
The Adapter Pattern is another valuable structural design pattern that allows objects with incompatible interfaces to collaborate. This pattern is often used when you want to make your existing class work with others without modifying their source code. The Adapter acts as a wrapper between two objects. It catches calls for one object and transforms them into calls for another, adapting the interface of one class (the adaptee) to be used from another interface, the one that the client expects to work with.
import logging
from patterns.proxy.proxy import Subject, RealSubject
class Adapter:
"""
Adapter to change RealSubject's interface.
We are using the Proxy pattern implementation from above.
"""
def __init__(self, proxy: Subject) -> None:
"""
Initialize Adapter with proxy.
Args:
proxy: Proxy interface to change.
Returns:
None
"""
self._proxy = proxy
def request(self) -> int:
"""
Change the interface of RealSubject via Proxy.
Returns:
Integer
"""
logging.info("Adapter: Changing the interface of RealSubject via Proxy.")
self._proxy.do_action()
return 1
def client_code(adapter: Adapter) -> str:
"""
Mock client code that works with all objects implementing the Adapter.
"""
return adapter.request()
# Executing client code with Adapter and Proxy.
real_subject = RealSubject()
proxy = Proxy(real_subject)
adapter = Adapter(proxy)
client_code(adapter) # --> 1
- The Decorator Design Pattern can also work in conjunction with Proxy. It adds responsibilities to an object, whereas the Proxy controls access to it. We have already discussed the Decorator Design Pattern here: https://medium.com/@tobiaslang/the-decorator-design-pattern-a-gateway-to-flexible-code-customization-f94cad1fbe1
from patterns.proxy.proxy import Subject, RealSubject
class Decorator:
"""
Decorator to add new responsibilities to the proxy object without changing its interface.
We are using the Proxy pattern implementation from above.
"""
def __init__(self, proxy: Subject) -> None:
"""
Initialize Decorator with proxy.
"""
self._proxy = proxy
def do_action(self) -> str:
"""
Add new responsibilities to the proxy object without changing its interface.
"""
print("Decorator: Before request.")
result = self._proxy.do_action()
print("Decorator: After request.")
return result
def client_code(decorator: Decorator) -> str:
"""
Mock client code that works with all objects implementing the Decorator.
"""
return decorator.do_action()
# Executing client code with Decorator and Proxy.
real_subject = RealSubject()
proxy = Proxy(real_subject)
decorator = Decorator(proxy)
result = client_code(decorator) # --> Proxy: Handling request.
- The Facade Pattern can also be combined with the Proxy pattern when you want to provide a simplified interface to a complex subsystem.
The Facade Pattern is a structural design pattern that provides a simplified interface to a complex subsystem. This pattern involves a single class that represents an entire subsystem, hiding its complexity from the client. The goal of the Facade Pattern is to improve the readability and usability of a software library, framework, or any other complex set of classes by providing a simpler, high-level interface. It does not alter the subsystem interface like an Adapter does; instead, it abstracts the subsystem’s complexity, providing a user-friendly layer for clients.
import logging
from patterns.proxy.proxy import Subject, RealSubject
class Facade:
"""
Facade to simplify access to the subsystem.
We are using the Proxy pattern implementation from above.
"""
def __init__(self, proxy: Subject) -> None:
"""
Initialize Facade with proxy.
"""
self._proxy = proxy
def operation(self) -> bool:
"""
Simplify access to the subsystem.
"""
logging.info("Accessing the subsystem..")
result = self._proxy.do_action()
if result:
return True
return False
def client_code(facade: Facade) -> bool:
"""
Mock client code that works with all objects implementing the Facade pattern.
"""
return facade.operation()
# Executing client code with Facade and Proxy.
real_subject = RealSubject()
proxy = Proxy(real_subject)
facade = Facade(proxy)
result = client_code(facade) # --> True
Real-World Examples in the Python Standard Library
An excellent real-world example of the Proxy Design Pattern in the Python Standard Library is the weakref
module. A weak reference is a pointer to an object that doesn't prevent the object from being garbage collected. Hence, it's a kind of proxy that references the original object without preventing it from dying.
import weakref
class HeavyObject:
def __init__(self, name):
self.name = name
heavy = HeavyObject('My Heavy Object')
weak_proxy = weakref.proxy(heavy)
print(weak_proxy.name)
In this case, the weakref.proxy
acts as a proxy to the HeavyObject
without preserving its lifetime. This allows for memory optimization, especially when dealing with large objects.
Another example is the lru_cache
decorator from the functools
module in the Python standard library. It provides a caching mechanism for function results, reducing the need to recompute them. The lru_cache
decorator acts as a proxy by intercepting function calls, caching the results, and returning the cached result for subsequent calls with the same arguments. It simplifies access to the underlying expensive computation and enhances performance.
And last but not least, the multiprocessing
module in Python includes the Pool
class, which provides a high-level interface for distributing tasks across multiple processes. The Pool
class acts as a proxy, managing access to a pool of worker processes and delegating tasks to them. The proxy hides the complexity of process creation, management, and communication, allowing the client code to focus on submitting and retrieving results from the worker processes.
Conclusion
The Proxy Design Pattern serves as a powerful tool in a developer’s toolkit, providing an efficient mechanism for controlled and simplified access to objects. It elegantly separates the responsibilities of object usage and object management, enabling higher code maintainability, security, and effective resource management.
Though the implementation of this pattern can lead to increased complexity and potential latency, understanding your application’s needs and resources will guide whether the Proxy Pattern is a good fit. A well-implemented Proxy Pattern can significantly enhance the structure and reliability of large systems.
The art of software design involves understanding the problem at hand and applying the most suitable pattern or mix of patterns to solve it. While Proxy is just one among many design patterns, its relevance and value in particular situations are unquestionable. Happy coding!