The Proxy Pattern is a structural design pattern that provides a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request reaches the original object.
The Problem
Imagine your website has a HeavyReport class that generates a massive data visualization. This process takes 10 seconds and consumes 500MB of RAM. If you instantiate this object every time the user enters the dashboard, the site will feel sluggish—even if the user never actually clicks the "View Report" button.
The Solution: The "Middleman"
Instead of giving the client the real object, you give them a Proxy. The proxy looks exactly like the real report (it implements the same interface).
- Lazy Loading: The proxy only creates the "Real Object" when the user actually calls
display(). - Access Control: The proxy can check if the user is an Admin before allowing the report to run.
- Logging: The proxy can record how many times the report was accessed.
Step-by-Step Java Implementation
1. The Interface
Both the Real Subject and the Proxy must implement this.
// Image.java
public interface Image {
void display();
}
2. The Real Subject
The heavy object we want to delay or protect.
// RealImage.java
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // Expensive operation
}
private void loadFromDisk() {
System.out.println("Loading " + filename + " (High Disk/CPU usage...)");
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
3. The Proxy
It holds a reference to the real object and controls its creation.
// ProxyImage.java
public class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
// Only initialize the heavy object when it is actually needed
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
Full Code for Testing
// Save as ProxyTest.java
interface VideoPlayer {
void play();
}
class RealVideoPlayer implements VideoPlayer {
private String fileName;
public RealVideoPlayer(String fileName) {
this.fileName = fileName;
this.loadVideo();
}
private void loadVideo() {
System.out.println("[System] Loading high-definition video: " + fileName);
}
@Override
public void play() {
System.out.println("[System] Playing video: " + fileName);
}
}
class ProxyVideoPlayer implements VideoPlayer {
private RealVideoPlayer realPlayer;
private String fileName;
public ProxyVideoPlayer(String fileName) {
this.fileName = fileName;
}
@Override
public void play() {
if (realPlayer == null) {
System.out.println("[Proxy] User clicked play. Initializing real player...");
realPlayer = new RealVideoPlayer(fileName);
}
realPlayer.play();
}
}
public class ProxyTest {
public static void main(String[] args) {
System.out.println("--- Proxy Pattern Test ---\n");
// We create the proxy. Notice the "Real" video is NOT loaded yet.
VideoPlayer video = new ProxyVideoPlayer("Tutorial_Design_Patterns.mp4");
System.out.println("Client: Proxy is ready, but video isn't loaded yet.");
System.out.println("... User waits for 5 minutes ...");
// Only now is the heavy RealVideoPlayer created.
video.play();
// Second call won't reload the video; it uses the existing real object.
System.out.println("\nClient: Playing video again.");
video.play();
System.out.println("\n--- Proxy Successful ---");
}
}
Why use Proxy?
- Efficiency: Virtual Proxies allow you to skip expensive initialization for objects that might not be used.
- Security: Protection Proxies can verify credentials before passing a call to a sensitive object.
- Remote Access: Remote Proxies can handle the complexity of network communication, making a remote object look like it's local.
Real-World Example
- Hibernate/JPA: When you fetch an entity from a database, it often returns a "Proxy" object. The actual data is only fetched from the database when you call a getter like
getName(). - Spring AOP: Spring uses proxies to handle
@Transactionalor@Loggedannotations, wrapping your code with extra logic.