Memory and time are the two main challenges while working with large objects. So, it is important to reuse these objects to generate new references rather than creating them on every new request. And most importantly, memory must be released after all sections of code have completed working on it.
A proxy design pattern is the best possible way to achieve the initialization of large objects. It separates the client code from the object by creating a surrogate proxy object that behaves like a real object. Here, the client code interacts with the proxy class – it has a real-object instance. Apart from object initialization, it also provides the best possible solutions for logging, network connections, access to shared objects, reference counting, and more.
Some advantages of using a proxy design pattern are:
- It ensures distributed, controlled, or intelligent access by using an extra level of indirection
- It protects the real object from unnecessary complexity by adding a wrapper.
- Avoids inapt object instantiation and can optimize the performance of an application
However, sometimes, the proxy pattern increases the response time from the object – when the object is requested for the first time, it will take more time for object initialization.
Implementation
Let’s implement a Python code to instantiate a large object, which holds 10 million digits – RealClass. Since it holds a large object, it is better to interact with the object using a proxy – ProxyClass – rather than initiating direct communication.
First and foremost, let’s create an abstract class – AbstractClass – that provides an interface for RealClass and ProxyClass. The abstract class has a method called sort_digits, and the real class inherits from the abstract class. The proxy class uses the instance of RealClass and facilitates client communication.
Python3
import abc import random class AbstractClass(metaclass = abc.ABCMeta): """ interface for real and proxy object """ @abc .abstractmethod def sort_digits( self , reverse = False ): pass class RealClass(AbstractClass): """ RealClass that holds a larger object """ def __init__( self ): self .digits = [] for i in range ( 1000000 ): self .digits.append(random.random()) def sort_digits( self , reverse = False ): self .digits.sort() if reverse: self .digits.reverse() class ProxyClass(AbstractClass): """ A proxy class that has the same interface as RealClass. """ ref_count = 0 def __init__( self ): """ Creates an object if it doesn't exist and caches it otherwise """ if not getattr ( self .__class__, 'cached_object' , None ): self .__class__.cached_object = RealClass() print ( 'New object generated' ) else : print ( 'Using cached object' ) self .__class__.ref_count + = 1 print ( 'Reference Count:' , self .__class__.ref_count) def sort_digits( self , reverse = False ): print ( 'Sort method' ) print ( locals ().items()) # invokes the sort_digits method of real class self .__class__.cached_object.sort_digits(reverse = reverse) def __del__( self ): """ Delete the object when the number of reference is 0 """ self .__class__.ref_count - = 1 if self .__class__.ref_count = = 0 : print ( 'Deleting cached object' ) del self .__class__.cached_object print ( 'Reference Count:' , self .__class__.ref_count) if __name__ = = '__main__' : proxA = ProxyClass() print () proxB = ProxyClass() print () proxC = ProxyClass() print () proxA.sort_digits(reverse = True ) print () print ( 'Deleting proxA' ) del proxA print ( 'Deleting proxB' ) del proxB print ( 'Deleting proxC' ) del proxC |
Output:
New object generated
Reference Count: 1Using cached object
Reference Count: 2Using cached object
Reference Count: 3Sort method
dict_items([(‘reverse’, True), (‘self’, <__main__.ProxyClass object at 0x7ff50f73e0b8>)])Deleting proxA
Reference Count: 2
Deleting proxB
Reference Count: 1
Deleting proxC
Deleting cached object
Reference Count: 0
Let’s look into the ProxyClass design. It creates an instance of the RealClass if it’s not been created before. If the object already exists, the proxy class increments the reference count and returns a new link to the real class. And the sort method in the proxy class calls the sort method of the real class, using the cached reference. In the end, the destructor method decreases the reference count on every call, and when there are no references left, it deletes the object.
Proxy design pattern optimizes the performance of an application by caching the frequently used objects and also improves the security of an application by checking the access rights. Apart from these, it facilitates remote system interactions.