The proxy design pattern is a software design pattern that provides a surrogate or placeholder for another object to control access to it. A proxy object is similar to the original object, but it doesn’t have all of the same functionality. The proxy object can forward requests to the original object, but it can also perform additional actions before or after the request is forwarded.
The proxy design pattern is often used to:
- Control access to a resource
- Provide a layer of abstraction
- Improve performance
- Handle errors
Use Case Examples
Here is an example of how the proxy design pattern can be used to control access to a resource. Let’s say you have a class that represents a database connection. You want to make sure that only authorized users can access the database. You can use the proxy design pattern to create a proxy object that checks the user’s authorization before forwarding the request to the real database connection object.
Here is another example of how the proxy design pattern can be used. Let’s say you have a class that represents a large image file. You want to improve the performance of your application by loading the image file only when it is needed. You can use the proxy design pattern to create a proxy object that loads the image file when it is first requested. The proxy object will then cache the image file so that it can be accessed quickly when it is requested again.
Code examples
Here is an example of a Java program that uses the proxy pattern to create a proxy object that represents a remote object:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote {
String sayHello() throws RemoteException;
}
public class HelloImpl implements Hello {
@Override
public String sayHello() throws RemoteException {
return "Hello, world!";
}
}
public class HelloProxy implements Hello {
private HelloImpl helloImpl;
public HelloProxy(String host, int port) throws RemoteException {
helloImpl = (HelloImpl) java.rmi.Naming.lookup("rmi://" + host + ":" + port + "/Hello");
}
@Override
public String sayHello() throws RemoteException {
return helloImpl.sayHello();
}
}
public class Main {
public static void main(String[] args) throws RemoteException {
Hello proxy = new HelloProxy("localhost", 1099);
System.out.println(proxy.sayHello());
}
}
In this example, the HelloImpl
class implements the Hello
interface. This interface defines a single method, sayHello()
, which returns a string. The HelloProxy
class implements the Hello
interface as well. However, it does not actually implement the sayHello()
method. Instead, it delegates the call to the sayHello()
method of the HelloImpl
object. The HelloProxy
class is created by passing the host and port of the remote object to the constructor. The main()
method creates a HelloProxy
object and calls the sayHello()
method. The sayHello()
method of the HelloProxy
class delegates the call to the sayHello()
method of the HelloImpl
object. The HelloImpl
object is located on the remote host and is accessible through the RMI protocol.
Here is another example
import java.util.concurrent.Callable;
public class ExpensiveObject {
private void initialize() {
System.out.println("Initializing expensive object...");
}
public void doSomethingExpensive() {
System.out.println("Doing something expensive...");
}
}
public class ExpensiveObjectProxy implements Callable<ExpensiveObject> {
private ExpensiveObject expensiveObject;
public ExpensiveObjectProxy() {
expensiveObject = new ExpensiveObject();
}
@Override
public ExpensiveObject call() throws Exception {
expensiveObject.initialize();
return expensiveObject;
}
}
public class Main {
public static void main(String[] args) throws Exception {
ExpensiveObjectProxy proxy = new ExpensiveObjectProxy();
ExpensiveObject expensiveObject = proxy.call();
expensiveObject.doSomethingExpensive();
}
}
In this example, the ExpensiveObject
class represents a heavy object. The ExpensiveObject
class has two methods: initialize()
and doSomethingExpensive()
. The initialize()
method initializes the object, and the doSomethingExpensive()
method does something expensive. The ExpensiveObjectProxy
class implements the Callable
interface. This interface defines a single method, call()
, which returns an object. The call()
method of the ExpensiveObjectProxy
class creates an ExpensiveObject
object and initializes it. The ExpensiveObject
object is returned by the call()
method. The main()
method creates an ExpensiveObjectProxy
object and calls the call()
method. The call()
method creates an ExpensiveObject
object and initializes it. The ExpensiveObject
object is returned by the call()
method. The main()
method then calls the doSomethingExpensive()
method of the ExpensiveObject
object.
The ExpensiveObjectProxy
class is a proxy object for the ExpensiveObject
object. The ExpensiveObjectProxy
class delegates the call to the initialize()
and doSomethingExpensive()
methods of the ExpensiveObject
object. The ExpensiveObjectProxy
class is used to avoid the overhead of initializing the ExpensiveObject
object if it is not needed.
Benefits of Proxy Design Pattern
The proxy design pattern is a powerful tool that can be used to solve a variety of problems. It is a good design pattern to know about if you are working on a software project that needs to control access to resources, provide a layer of abstraction, improve performance, or handle errors.
Here are some of the benefits of using the proxy design pattern:
- Increased performance: A proxy can improve performance by caching data or by performing tasks in advance.
- Improved security: A proxy can improve security by controlling access to resources or by preventing unauthorized modifications.
- Increased flexibility: A proxy can make it easier to change the implementation of an object without affecting its clients.
- Reduced coupling: A proxy can reduce coupling between objects by providing a layer of abstraction.
Drawbacks of Proxy Design Pattern
Here are some of the drawbacks of using the proxy design pattern:
- Increased complexity: A proxy can add complexity to a system by introducing additional objects and interfaces.
- Increased overhead: A proxy can add overhead to a system by performing additional tasks, such as caching data or checking permissions.
- Potential for errors: If a proxy is not implemented correctly, it can introduce errors into a system.
Conclusion
Overall, the proxy design pattern is a powerful tool that can be used to solve a variety of problems. However, it is important to weigh the benefits and drawbacks of using the proxy design pattern before deciding whether or not to use it.
Further Readings
Below are some textbooks to refer to to grasp more understanding of the Proxy Design Pattern:
- Design Patterns: Elements of Reusable Object-Oriented Software by the Gang of Four (GoF) is a classic book that covers all of the major design patterns, including the proxy pattern.
- Head First Design Patterns by Elisabeth Freeman and Kathy Sierra is a more approachable book that uses a fun and engaging style to teach design patterns including Proxy patterns.
- Effective Java 3rd Edition by Joshua Bloch covers the proxy pattern in Chapter 17, “Caching”.
- Java Design Patterns by Martin Fowler is a comprehensive book that covers all of the major design patterns, including the proxy pattern.
- https://en.wikipedia.org/wiki/Proxy_pattern
- Checkout our other blogs on Design Patterns including Creational Design Patterns & Structural Design Patterns
Feel free to share your thoughts on this topic in the comments section below 👇 We would be happy to hear and discuss the same 🙂