Proxy design pattern in Java and Python Development 04.01.2017

Structural Design Patterns provide different ways to create a class structure, for example using inheritance and composition to create a large object from small objects. Following design patterns come under this category.

Using the proxy pattern, a class represents the functionality of another class.

The Proxy Pattern is used to create a representative object that controls access to another object, which may be remote, expensive to create or in need of being secured. The Proxy can be very useful in controlling the access to the original object, especially when objects should have different access rights.

In the Proxy Pattern, a client does not directly talk to the original object, it delegates it calls to the proxy object which calls the methods of the original object. The important point is that the client does not know about the proxy, the proxy acts as an original object for the client.

dp_proxy.png

The participants classes in the proxy pattern are:

  • Subject. Interface implemented by the RealSubject and representing its services. The interface must be implemented by the proxy as well so that the proxy can be used in any location where the RealSubject can be used.
  • Proxy. Maintains a reference that allows the Proxy to access the RealSubject. Implements the same interface implemented by the RealSubject so that the Proxy can be substituted for the RealSubject. Controls access to the RealSubject and may be responsible for its creation and deletion. Other responsibilities depend on the kind of proxy.
  • RealSubject. Real object that the proxy represents.

There are three main variations to the Proxy pattern

  • A remote proxy provides a local representative for an object in a different address space.
  • A virtual proxy creates expensive objects on demand.
  • A protection proxy controls access to the original object. Protection proxies are useful when objects should have different access rights.

Let's take a look at this in action with a sequence diagram.

dp_proxy_seq.png

When to use the Proxy pattern

Proxy is applicable whenever there is a need for a more versatile or sophisticated reference to an object than a simple pointer. Here are several common situations in which the Proxy pattern is applicable:

  • A remote proxy provides a local representative for an object in a different address space.
  • A virtual proxy creates expensive objects on demand.
  • A protection proxy controls access to the original object. Protection proxies are useful when objects should have different access rights.

Java

Let’s say we have a class that can run some command on the system. Now if we are using it, its fine but if we want to give this program to a client application, it can have severe issues because client program can issue command to delete some system files or change some settings that you don’t want.

Here a proxy class can be created to provide controlled access of the program.

public interface CommandExecutor {
    public void runCommand(String cmd) throws Exception;
}
import java.io.IOException;

public class CommandExecutorImpl implements CommandExecutor {
    @Override
    public void runCommand(String cmd) throws IOException {
        //some heavy implementation
        Runtime.getRuntime().exec(cmd);
        System.out.println("'" + cmd + "' command executed.");
    }
}

Now we want to provide only admin users to have full access of above class, if the user is not admin then only limited commands will be allowed. Here is our very simple proxy class implementation.

public class CommandExecutorProxy implements CommandExecutor {
    private boolean isAdmin;
    private CommandExecutor executor;

    public CommandExecutorProxy(String user, String pwd){
        if("Admin".equals(user) && "qwerty".equals(pwd)) isAdmin = true;
        executor = new CommandExecutorImpl();
    }

    @Override
    public void runCommand(String cmd) throws Exception {
        if(isAdmin){
            executor.runCommand(cmd);
        }else{
            if(cmd.trim().startsWith("rm")){
                throw new Exception("rm command is not allowed for non-admin users.");
            }else{
                executor.runCommand(cmd);
            }
        }
    }
}
public class ProxyPatternTest {
    public static void main(String[] args){
        CommandExecutor executor = new CommandExecutorProxy("Admin", "123456");
        try {
            executor.runCommand("ls -ltr");
            executor.runCommand("rm -rf abc.pdf");
        } catch (Exception e) {
            System.out.println("Exception Message:: " + e.getMessage());
        }
    }
}

Python

import abc

class CommandExecutor(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def runCommand(self, cmd):
        pass

class CommandExecutorImpl(CommandExecutor):
    def runCommand(self, cmd):
        print("{0} command executed.".format(cmd))


class CommandExecutorProxy(CommandExecutor):
    def __init__(self, user, pwd):
        self.is_admin = False
        if "Admin" == user and "qwerty" == pwd:
            self.is_admin = True
        self.executor = CommandExecutorImpl()

    def runCommand(self, cmd):
        if self.is_admin:
            self.executor.runCommand(cmd)
        else:
            if cmd.strip().startswith("rm"):
                raise Exception("rm command is not allowed for non-admin users.")
            else:
                self.executor.runCommand(cmd)


if __name__ == '__main__':
    executor = CommandExecutorProxy("Admin", "123456")
    try:
        executor.runCommand("ls -ltr");
        executor.runCommand("rm -rf abc.pdf");
    except Exception as e:
        print(e)