Home/Design Patterns/Proxy Pattern

Proxy Pattern

🎯 Explain Like I'm 5...

Imagine you want to meet a famous celebrity, but you can't just walk up to them! You need to talk to their bodyguard first! 💂

😟 The Problem:

  • You want to access something expensive or protected
  • Direct access might be slow, dangerous, or not allowed
  • You need someone to control or manage that access 🚫

💡 The Solution - Use a Proxy (Bodyguard)!

  • Create a proxy that stands between you and the real object! 🛡️
  • The proxy controls access - checks permissions, loads things when needed
  • The proxy looks just like the real object to you
  • You don't even know you're talking to a proxy, not the real thing! 🎭

🌟 The Key Idea:

A proxy is like a bodyguard or assistant! It controls access to the real object, can load things lazily, cache results, or check permissions. It's a SUBSTITUTE that protects or manages the real thing! 🛡️

🚀 Why Is This Pattern Useful?

  • Delay expensive operations until they're actually needed (lazy loading)!
  • Add security and access control without changing the real object!
  • Cache results to make repeated operations faster!
  • Add logging, monitoring, or extra functionality transparently!

📋 Pattern Purpose

The Proxy pattern provides a surrogate or placeholder for another object to control access to it. The proxy has the same interface as the real object, so clients don't know they're using a proxy instead of the real thing.

⚡ When to Use Proxy Pattern

  • You want to delay creating expensive objects until they're actually needed (Virtual Proxy)
  • You need to control access based on permissions (Protection Proxy)
  • You want to cache results of expensive operations (Caching Proxy)
  • You need to access remote objects as if they were local (Remote Proxy)
  • You want to add logging or monitoring without changing the original object

🔄 Types of Proxies

Virtual Proxy (Lazy Loading):

Delays creation of expensive objects until needed. Like loading a huge image only when you scroll to it!

Protection Proxy (Access Control):

Controls access based on permissions. Like a security guard checking your ID!

Remote Proxy:

Represents objects in different address spaces. Like accessing a server object as if it's local!

Caching Proxy:

Stores results of expensive operations and reuses them. Like remembering answers to avoid recalculating!

💻 Java Implementations

Example 1: Virtual Proxy (Lazy Loading Images)

Loading large images only when they're actually displayed, not when the object is created.

Image.java
java
1
2
3
4
5
// Subject Interface
public interface Image {
void display();
String getFileName();
}
RealImage.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Real Subject - Expensive to create
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk();
}
// This is EXPENSIVE - takes time and memory!
private void loadFromDisk() {
System.out.println("Loading image from disk: " + fileName);
// Simulate expensive operation
try {
Thread.sleep(2000); // Pretend loading takes 2 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void display() {
System.out.println("Displaying image: " + fileName);
}
@Override
public String getFileName() {
return fileName;
}
}
ProxyImage.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Proxy - Delays loading until display() is called
public class ProxyImage implements Image {
private String fileName;
private RealImage realImage; // Lazy initialization
public ProxyImage(String fileName) {
this.fileName = fileName;
System.out.println("ProxyImage created for: " + fileName);
// Note: RealImage is NOT created yet!
}
@Override
public void display() {
// Load the real image only when needed
if (realImage == null) {
System.out.println("First time displaying - loading now!");
realImage = new RealImage(fileName);
}
realImage.display();
}
@Override
public String getFileName() {
return fileName;
}
}
VirtualProxyDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Client code
public class VirtualProxyDemo {
public static void main(String[] args) {
System.out.println("=== Creating image objects ===");
// Create proxies - FAST! Real images not loaded yet
Image image1 = new ProxyImage("vacation.jpg");
Image image2 = new ProxyImage("family.jpg");
Image image3 = new ProxyImage("friends.jpg");
System.out.println("\n=== All proxies created! ===");
System.out.println("\n=== Now displaying image1 ===");
// SLOW - loads from disk on first display
image1.display();
System.out.println("\n=== Displaying image1 again ===");
// FAST - already loaded!
image1.display();
System.out.println("\n=== Displaying image2 ===");
// SLOW - first time
image2.display();
// image3 is never displayed, so it's never loaded!
// Saved time and memory!
}
}
/* Output:
=== Creating image objects ===
ProxyImage created for: vacation.jpg
ProxyImage created for: family.jpg
ProxyImage created for: friends.jpg
=== All proxies created! ===
=== Now displaying image1 ===
First time displaying - loading now!
Loading image from disk: vacation.jpg
Displaying image: vacation.jpg
=== Displaying image1 again ===
Displaying image: vacation.jpg
=== Displaying image2 ===
First time displaying - loading now!
Loading image from disk: family.jpg
Displaying image: family.jpg
*/

Example 2: Protection Proxy (Document Access Control)

Controlling who can read or write documents based on user roles.

User.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// User class with roles
public class User {
private String name;
private String role; // "ADMIN", "EDITOR", "VIEWER"
public User(String name, String role) {
this.name = name;
this.role = role;
}
public String getName() {
return name;
}
public String getRole() {
return role;
}
}
Document.java
java
1
2
3
4
5
6
// Subject Interface
public interface Document {
void displayContent();
void editContent(String newContent);
void deleteDocument();
}
RealDocument.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Real Subject - The actual document
public class RealDocument implements Document {
private String fileName;
private String content;
public RealDocument(String fileName, String content) {
this.fileName = fileName;
this.content = content;
}
@Override
public void displayContent() {
System.out.println("=== Document: " + fileName + " ===");
System.out.println(content);
System.out.println("========================");
}
@Override
public void editContent(String newContent) {
this.content = newContent;
System.out.println("Document edited successfully!");
}
@Override
public void deleteDocument() {
System.out.println("Document '" + fileName + "' deleted!");
this.content = null;
}
}
DocumentProxy.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Protection Proxy - Controls access based on user role
public class DocumentProxy implements Document {
private RealDocument realDocument;
private User user;
private String fileName;
private String content;
public DocumentProxy(String fileName, String content, User user) {
this.fileName = fileName;
this.content = content;
this.user = user;
}
// Lazy load the real document
private RealDocument getRealDocument() {
if (realDocument == null) {
realDocument = new RealDocument(fileName, content);
}
return realDocument;
}
@Override
public void displayContent() {
// Everyone can view
System.out.println("[Access Check] User '" + user.getName() +
"' (" + user.getRole() + ") requesting to VIEW");
getRealDocument().displayContent();
}
@Override
public void editContent(String newContent) {
// Only ADMIN and EDITOR can edit
System.out.println("[Access Check] User '" + user.getName() +
"' (" + user.getRole() + ") requesting to EDIT");
if (user.getRole().equals("ADMIN") || user.getRole().equals("EDITOR")) {
System.out.println("[Access Granted] Edit allowed!");
getRealDocument().editContent(newContent);
} else {
System.out.println("[Access Denied] Only ADMIN or EDITOR can edit!");
}
}
@Override
public void deleteDocument() {
// Only ADMIN can delete
System.out.println("[Access Check] User '" + user.getName() +
"' (" + user.getRole() + ") requesting to DELETE");
if (user.getRole().equals("ADMIN")) {
System.out.println("[Access Granted] Delete allowed!");
getRealDocument().deleteDocument();
} else {
System.out.println("[Access Denied] Only ADMIN can delete!");
}
}
}
ProtectionProxyDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Client code
public class ProtectionProxyDemo {
public static void main(String[] args) {
// Create users with different roles
User admin = new User("Alice", "ADMIN");
User editor = new User("Bob", "EDITOR");
User viewer = new User("Charlie", "VIEWER");
String content = "This is a confidential document.";
System.out.println("=== ADMIN trying to access document ===\n");
Document adminDoc = new DocumentProxy("secret.txt", content, admin);
adminDoc.displayContent();
adminDoc.editContent("Updated by admin!");
adminDoc.deleteDocument();
System.out.println("\n=== EDITOR trying to access document ===\n");
Document editorDoc = new DocumentProxy("report.txt", content, editor);
editorDoc.displayContent();
editorDoc.editContent("Updated by editor!");
editorDoc.deleteDocument(); // Will be denied!
System.out.println("\n=== VIEWER trying to access document ===\n");
Document viewerDoc = new DocumentProxy("public.txt", content, viewer);
viewerDoc.displayContent();
viewerDoc.editContent("Trying to edit..."); // Will be denied!
viewerDoc.deleteDocument(); // Will be denied!
}
}
/* Output:
=== ADMIN trying to access document ===
[Access Check] User 'Alice' (ADMIN) requesting to VIEW
=== Document: secret.txt ===
This is a confidential document.
========================
[Access Check] User 'Alice' (ADMIN) requesting to EDIT
[Access Granted] Edit allowed!
Document edited successfully!
[Access Check] User 'Alice' (ADMIN) requesting to DELETE
[Access Granted] Delete allowed!
Document 'secret.txt' deleted!
=== EDITOR trying to access document ===
[Access Check] User 'Bob' (EDITOR) requesting to VIEW
=== Document: report.txt ===
This is a confidential document.
========================
[Access Check] User 'Bob' (EDITOR) requesting to EDIT
[Access Granted] Edit allowed!
Document edited successfully!
[Access Check] User 'Bob' (EDITOR) requesting to DELETE
[Access Denied] Only ADMIN can delete!
=== VIEWER trying to access document ===
[Access Check] User 'Charlie' (VIEWER) requesting to VIEW
=== Document: public.txt ===
This is a confidential document.
========================
[Access Check] User 'Charlie' (VIEWER) requesting to EDIT
[Access Denied] Only ADMIN or EDITOR can edit!
[Access Check] User 'Charlie' (VIEWER) requesting to DELETE
[Access Denied] Only ADMIN can delete!
*/

Example 3: Caching Proxy (Expensive Calculations)

Caching results of expensive mathematical operations to avoid redundant calculations.

MathOperations.java
java
1
2
3
4
5
// Subject Interface
public interface MathOperations {
long factorial(int n);
long fibonacci(int n);
}
ExpensiveMathOperations.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Real Subject - Expensive calculations
public class ExpensiveMathOperations implements MathOperations {
@Override
public long factorial(int n) {
System.out.println(" [Computing factorial of " + n + "]");
// Simulate expensive operation
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (n <= 1) return 1;
return n * factorial(n - 1);
}
@Override
public long fibonacci(int n) {
System.out.println(" [Computing fibonacci of " + n + "]");
// Simulate expensive operation
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
CachingMathProxy.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.util.HashMap;
import java.util.Map;
// Caching Proxy - Stores results to avoid recomputation
public class CachingMathProxy implements MathOperations {
private ExpensiveMathOperations realMath;
private Map<String, Long> cache;
public CachingMathProxy() {
this.realMath = new ExpensiveMathOperations();
this.cache = new HashMap<>();
}
@Override
public long factorial(int n) {
String key = "factorial_" + n;
// Check cache first
if (cache.containsKey(key)) {
System.out.println("✓ Cache HIT for factorial(" + n + ")");
return cache.get(key);
}
// Cache miss - compute and store
System.out.println("✗ Cache MISS for factorial(" + n + ")");
long result = realMath.factorial(n);
cache.put(key, result);
return result;
}
@Override
public long fibonacci(int n) {
String key = "fibonacci_" + n;
// Check cache first
if (cache.containsKey(key)) {
System.out.println("✓ Cache HIT for fibonacci(" + n + ")");
return cache.get(key);
}
// Cache miss - compute and store
System.out.println("✗ Cache MISS for fibonacci(" + n + ")");
long result = realMath.fibonacci(n);
cache.put(key, result);
return result;
}
// Method to show cache statistics
public void showCacheStats() {
System.out.println("\n=== Cache Statistics ===");
System.out.println("Cached entries: " + cache.size());
cache.forEach((key, value) ->
System.out.println(" " + key + " = " + value));
}
}
CachingProxyDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// Client code
public class CachingProxyDemo {
public static void main(String[] args) {
CachingMathProxy math = new CachingMathProxy();
System.out.println("=== First factorial(5) call ===");
long result1 = math.factorial(5);
System.out.println("Result: " + result1);
System.out.println("\n=== Second factorial(5) call - INSTANT! ===");
long result2 = math.factorial(5);
System.out.println("Result: " + result2);
System.out.println("\n=== First fibonacci(7) call ===");
long result3 = math.fibonacci(7);
System.out.println("Result: " + result3);
System.out.println("\n=== Second fibonacci(7) call - INSTANT! ===");
long result4 = math.fibonacci(7);
System.out.println("Result: " + result4);
System.out.println("\n=== New calculation factorial(6) ===");
long result5 = math.factorial(6);
System.out.println("Result: " + result5);
// Show cache statistics
math.showCacheStats();
}
}
/* Output:
=== First factorial(5) call ===
Cache MISS for factorial(5)
[Computing factorial of 5]
[Computing factorial of 4]
[Computing factorial of 3]
[Computing factorial of 2]
[Computing factorial of 1]
Result: 120
=== Second factorial(5) call - INSTANT! ===
Cache HIT for factorial(5)
Result: 120
=== First fibonacci(7) call ===
Cache MISS for fibonacci(7)
[Computing fibonacci of 7]
[Computing fibonacci of 6]
[Computing fibonacci of 5]
[Computing fibonacci of 4]
[Computing fibonacci of 3]
[Computing fibonacci of 2]
[Computing fibonacci of 1]
[Computing fibonacci of 0]
Result: 13
=== Second fibonacci(7) call - INSTANT! ===
Cache HIT for fibonacci(7)
Result: 13
=== New calculation factorial(6) ===
Cache MISS for factorial(6)
[Computing factorial of 6]
Result: 720
=== Cache Statistics ===
Cached entries: 3
factorial_5 = 120
fibonacci_7 = 13
factorial_6 = 720
*/

🌍 Real-World Examples

  • 🌐Web Proxies: Cache web pages to speed up browsing
  • 🖼️Virtual Proxies: Lazy-load images in web pages and apps
  • 🔒Security Proxies: Firewall, authentication, authorization systems
  • 🔗Smart References: Reference counting, lazy loading in ORMs
  • 🏧ATM Machines: Act as proxy for your bank account

✅ Benefits

  • Lazy Initialization: Delays expensive operations until necessary
  • Access Control: Adds security without modifying original object
  • Performance: Caching and optimization without changing core logic
  • Transparency: Clients don't need to know they're using a proxy
  • Additional Functionality: Add logging, monitoring, etc. easily

⚠️ Drawbacks

  • ⚠️Complexity: Adds extra classes and indirection
  • ⚠️Response Time: May introduce slight delays
  • ⚠️Maintenance: Need to keep proxy and real object in sync

🔑 Key Points to Remember

  • 1️⃣Proxy controls access to the real object (like a bodyguard!)
  • 2️⃣Proxy has the same interface as the real object
  • 3️⃣Client doesn't know if it's using proxy or real object
  • 4️⃣Different from Decorator - Proxy controls access, Decorator adds behavior
  • 5️⃣Different from Adapter - Proxy keeps same interface, Adapter changes it

💪 Practice Scenarios

  • Create a virtual proxy for loading high-resolution images in a gallery app
  • Implement a protection proxy for a file system with different user permissions
  • Build a caching proxy for database queries to reduce server load
  • Design a logging proxy that tracks all method calls on an object
  • Create a remote proxy for accessing microservices across network