Python Proxy Design Pattern

1. Definition

The Proxy Design Pattern provides a surrogate or placeholder for another object to control access to it. It acts as an intermediary interface to an object and can add additional functionalities to the object without changing its structure.

2. Problem Statement

Imagine you have a system where some operations are resource-intensive, expensive, or require additional security checks. Instantiating or invoking these operations directly might not be optimal or secure.

3. Solution

The solution is to use a proxy that acts as a substitute to the real object. The proxy can handle the instantiation of the expensive object, ensuring it's only created when truly necessary. It can also add pre- or post-invocation steps to the actual method calls, offering additional layers of security or optimization.

4. Real-World Use Cases

1. Lazy loading of large-sized images in a graphic editor.

2. Providing controlled access to files or resources.

3. Caching information for faster retrieval.

4. Monitoring or logging access to certain components.

5. Implementation Steps

1. Define an interface that both the Real Object and Proxy classes will implement.

2. Create the Real Object class that performs the actual operations.

3. Design the Proxy class, which holds a reference to the Real Object and can control access to it.

4. In the client code, use the Proxy instead of the Real Object.

6. Implementation in Python

# Step 1: Define the common interface
class Image:
    def display(self):
        pass
# Step 2: Create the Real Object class
class RealImage(Image):
    def __init__(self, filename):
        self._filename = filename
        self._load_image_from_disk()
    def _load_image_from_disk(self):
        print(f"Loading image: {self._filename}")
    def display(self):
        print(f"Displaying image: {self._filename}")
# Step 3: Design the Proxy class
class ProxyImage(Image):
    def __init__(self, filename):
        self._filename = filename
        self._real_image = None
    def display(self):
        if self._real_image is None:
            self._real_image = RealImage(self._filename)
        self._real_image.display()
# Step 4: Client code
image1 = ProxyImage("sample.jpg")
image1.display()  # Image will be loaded here
image1.display()  # Image will NOT be loaded again, cached by proxy

Output:

"Loading image: sample.jpg"
"Displaying image: sample.jpg"
"Displaying image: sample.jpg"

Explanation:

1. Image is the common interface for RealImage and ProxyImage.

2. RealImage is the class that does the actual work (loading and displaying the image in this example).

3. ProxyImage acts as a substitute for RealImage. It caches an instance of RealImage and defers the loading until it's really necessary.

4. When display is called on the proxy for the first time, the image is loaded. On subsequent calls, the already loaded image is displayed, demonstrating lazy loading.

5. The proxy adds the additional behavior of lazy loading, without changing the core functionalities of the RealImage.

7. When to use?

The Proxy Pattern is beneficial when:

1. You want to act as an intermediary for another object for controlled access or lazy instantiation.

2. You want to add additional functionalities, like caching or logging, without altering the object's code.

3. You want a more sophisticated or ready-to-use reference to an object than a simple pointer.


Comments