Proxy Design Pattern in Java

1. Definition

The Proxy Design Pattern provides a surrogate or placeholder for another object to control access to it. It can add a level of indirection by accepting requests from a client object and passing these to the real object hidden behind it.

2. Problem Statement

Imagine you have a heavy object that consumes a lot of resources or takes considerable time to load. You don't want to instantiate this object unless it's absolutely necessary. Moreover, you want to control the access to this object.

3. Solution

The Proxy pattern provides a way to control access to the actual object by acting as an intermediary, or "proxy". This proxy can be used to delay the instantiation of the actual object until it's really needed, to access it remotely, or to add certain behaviors when the object is accessed.

4. Real-World Use Cases

1. Lazy instantiation for resource-heavy objects.

2. Access controls for sensitive operations in an object.

3. Providing a local representation for an object that resides in a different address space (remote proxy).

4. Logging activities when an object is accessed.

5. Implementation Steps

1. Create an interface that both the RealObject and Proxy will implement.

2. Implement the RealObject that represents the actual object.

3. Implement the Proxy which holds a reference to the RealObject and controls access to it.

6. Implementation

// Step 1: Define the interface
interface Image {
    void display();
}

// Step 2: Implement the RealObject
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}

// Step 3: Implement the Proxy
class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if(realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

// Client code
public class ProxyPatternDemo {
    public static void main(String[] args) {
        Image image = new ProxyImage("testImage.jpg");

        // Image will be loaded from disk
        image.display();
        System.out.println("");

        // Image will not be loaded from disk
        image.display();
    }
}

Output:

Loading testImage.jpg
Displaying testImage.jpg
Displaying testImage.jpg

Explanation

In this example, ProxyImage is a proxy to reduce the memory footprint of RealImage until it's required to be displayed. When calling display() for the first time, the image gets loaded from disk. However, on subsequent calls, the already loaded RealImage is used, and there's no need to reload it from disk.

7. When to use?

Use the Proxy pattern when:

1. You need a more versatile or sophisticated reference to an object than just a pointer.

2. You want to postpone the instantiation of a heavy object until it's really necessary.

3. You need to control access to the actual object for various reasons, such as accessing it remotely or adding behaviors like logging.


Comments