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.
// Subject Interfacepublic interface Image { void display(); String getFileName();}// Real Subject - Expensive to createpublic 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; }}// Proxy - Delays loading until display() is calledpublic 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; }}// Client codepublic 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.jpgProxyImage created for: family.jpgProxyImage created for: friends.jpg=== All proxies created! ====== Now displaying image1 ===First time displaying - loading now!Loading image from disk: vacation.jpgDisplaying image: vacation.jpg=== Displaying image1 again ===Displaying image: vacation.jpg=== Displaying image2 ===First time displaying - loading now!Loading image from disk: family.jpgDisplaying image: family.jpg*/Example 2: Protection Proxy (Document Access Control)
Controlling who can read or write documents based on user roles.
// User class with rolespublic 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; }}// Subject Interfacepublic interface Document { void displayContent(); void editContent(String newContent); void deleteDocument();}// Real Subject - The actual documentpublic 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; }}// Protection Proxy - Controls access based on user rolepublic 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!"); } }}// Client codepublic 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.
// Subject Interfacepublic interface MathOperations { long factorial(int n); long fibonacci(int n);}// Real Subject - Expensive calculationspublic 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); }}import java.util.HashMap;import java.util.Map;// Caching Proxy - Stores results to avoid recomputationpublic 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)); }}// Client codepublic 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