Proxy Design Pattern

The Proxy design pattern is simply an intermediary that is designed over a class to add some additional functionality, without modifying the original class.

The Proxy pattern tells us to build a new proxy class with the same interface as the original class. Then instead of using the original class, we used the proxy class on all clients that used the original class.
The proxy class adds the desired functionality and then delegates all the work to the original class. To create this pattern, we need to define an interface from which to extend the main class. We will then create our proxy class, which will also extend from the interface and have the main class as its attribute.

What are the parts of the Proxy design pattern?

This pattern consists mainly of three parts:

  • Interface: from which the main class will be created.
  • Main class: implementing the interface.
  • Proxy class: the class that will also extend from the interface and which will contain the main class as an attribute.

proxy-pattern

How to create the Proxy design pattern in Java

To better understand this pattern, let’s look at an example. Suppose you have a class that performs processes for which you have to read a large file on disk. We want this class to lift the disk file only once, but we do not have access to modify the original class.

We will create from the interface our class. In addition to this same interface, we will implement the proxy. Instead of querying the actual class, the client will do so through the proxy.

proxy-pattern

We have our interface from which extends the original class.

package patterns.proxy;

public interface BatchProcess {

    void process();

}

The class that performs the process in the constructor reads the file from the disk.

In this class, we simulate in the method loadFromDisk the work of reading the file.


package patterns.proxy;

public class BatchProcessImpl implements BatchProcess {

    private final String fileName;

    public BatchProcessImpl(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }

    @Override
    public void process() {
        System.out.println("Processing file " + fileName);
    }

    private void loadFromDisk(String fileName) {
        System.out.println("Expensive and hard process...");

        for (int i = 0; i < 10; i++) {
            System.out.println("Loading " + fileName + " " + i * 10 + "%");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }

        System.out.println("Loaded " + fileName);
    }
}


We know that it is expensive to read in class BatchProcessImpl, and we would like it to be done only once.

We do not have access to modify the class BatchProcessImpl, so we decided to create a proxy class.

The proxy class implements the same interface BatchProcess and will have the class BatchProcessImpl

package patterns.proxy;

public class ProxyBatchProcess implements BatchProcess {

    private BatchProcessImpl batchProcess;
    private String fileName;

    public ProxyBatchProcess(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void process() {
        if (batchProcess == null) {
            batchProcess = new BatchProcessImpl(fileName);
        }
        batchProcess.process();
    }
}

We now try the Proxy design pattern

  • We created the ProxyBatchProcess object by sending a large file.
  • We run for the first time and see in the output that the file is read.
  • Then we run again, and this time we can check that the file is no longer read and only the process is performed on the file previously ‘that was read from the disk.’
package patterns.proxy;

public class ProxyPatternExample {

    public static void main(String[] args) {

        BatchProcess batchProcess = new ProxyBatchProcess("big_file.txt");

        //batchProcess will be loaded from disk
        System.out.println("BatchProcess will be loaded from disk");
        batchProcess.process();

        System.out.println("-----------------");

        //batchProcess will not be loaded from disk
        System.out.println("BatchProcess will not be loaded from disk");
        batchProcess.process();
    }
}

In the output, we see that the first execution ‘loads’ the file and in the second the process is performed without re-loading it as we wanted.


BatchProcess will be loaded from disk
Expensive and hard process...
Loading big_file.txt 0%
Loading big_file.txt 10%
Loading big_file.txt 20%
Loading big_file.txt 30%
Loading big_file.txt 40%
Loading big_file.txt 50%
Loading big_file.txt 60%
Loading big_file.txt 70%
Loading big_file.txt 80%
Loading big_file.txt 90%
Loaded big_file.txt
Processing file big_file.txt
-----------------
BatchProcess will not be loaded from disk
Processing file big_file.txt

Some advantages and disadvantages of this pattern:

  • We can modify functionality without affecting other functionalities that are using the original class directly.
  • The code can become complicated if you need to introduce lots of new functionality.
  • The actual response of the service may be unknown until it is actually invoked.

Conclusion:

We’ve seen the Proxy pattern and how useful it can be to add functionality to a class without modifying it.

You can review this code GitHub

Hi! If you find my posts helpful, please support me by inviting me for a coffee :)

See also