Proxy Design Pattern in Python
The proxy design pattern is a structural pattern that provides a placeholder object for another object to control access to it. This pattern is often used in cases where creating an object is either resource-intensive or not possible for some other reason.
In the proxy pattern, a proxy object acts as an intermediary between the client and the real object. The proxy object has the same interface as the real object and can be used in place of the real object in the client code. The proxy can also perform additional tasks, such as caching or logging, before or after forwarding requests to the real object.
An example of when the proxy pattern can be useful is when working with remote objects. By using a proxy object to access the remote object, we can cache the results of remote method calls, improve performance, and make the code more robust.
In Python, the proxy pattern can be implemented using inheritance or by using a class that holds a reference to the real object. Here is an example of how to implement the proxy pattern using inheritance:
class AbstractHandler:
def __init__(self, successor=None):
self._successor = successor
def handle(self, request):
handled = self._handle(request)
if not handled:
self._successor.handle(request)
def _handle(self, request):
raise NotImplementedError('Must provide implementation in subclass.')
class ConcreteHandler1(AbstractHandler):
def _handle(self, request):
if 0 < request <= 10:
print(f'Request {request} handled in handler 1')
return True
class ConcreteHandler2(AbstractHandler):
def _handle(self, request):
if 10 < request <= 20:
print(f'Request {request} handled in handler 2')
return True
class ConcreteHandler3(AbstractHandler):
def _handle(self, request):
if 20 < request <= 30:
print(f'Request {request} handled in handler 3')
return True
def main():
h1 = ConcreteHandler1()
h2 = ConcreteHandler2(h1)
h3 = ConcreteHandler3(h2)
requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
for request in requests:
h3.handle(request)
if __name__ == '__main__':
main()
In this example, the AbstractHandler
class defines the basic structure for handling requests and contains a reference to the next object in the chain. The handle
method attempts to handle the request and if it is unable to do so, it passes the request along to the next object in the chain. The _handle
method must be implemented by subclasses to handle specific requests.
The ConcreteHandler1
, ConcreteHandler2
, and ConcreteHandler3
classes are concrete implementations of the AbstractHandler
class and each handle different ranges of requests. In the main
function, an instance of each concrete handler is created and linked together to form the chain. Finally, a series of requests is sent through the chain and handled by the appropriate handler.
In conclusion, the Chain of Responsibility pattern is a useful pattern for handling requests in a flexible and dynamic way. By decoupling the sender of a request from the receiver, it allows for the objects in the chain to be rearranged and modified dynamically, making it well-suited for complex and evolving systems.