# Proxy Pattern > Provide a surrogate or placeholder for another object to control access to it. ## Problem You need to control how an object is accessed. Common scenarios: 1. **Virtual proxy** — Delay expensive object creation until actually needed (lazy loading). 2. **Protection proxy** — Restrict access based on permissions. 3. **Logging proxy** — Log all interactions transparently. ## Solution Create a proxy class with the same interface as the real object. The proxy holds a reference to the real object and adds control logic (lazy init, access checks, logging) before/after delegating calls. ``` ┌────────┐ ┌──────────────────┐ │ Client │─────▶│ Service (intf) │ └────────┘ │ + request() │ └──────┬───────────┘ │ ┌───────┴───────┐ │ │ ┌────┴────┐ ┌─────┴─────┐ │RealServ │ │ Proxy │ │ │ │ + real │ └─────────┘ │ + request│ └──────────┘ ``` ## When to Use - **Virtual proxy**: The real object is expensive to create and may not always be needed. - **Protection proxy**: You need to check permissions before granting access. - **Logging proxy**: You want to record interactions without modifying the real object. - **Caching proxy**: You want to cache results of expensive operations. ## When to Avoid - The added indirection is unnecessary for simple, inexpensive objects. - The proxy would duplicate logic that belongs in the real object. - You need to proxy many methods and a language-level mechanism (e.g., Python `__getattr__`, JS `Proxy`) would be cleaner. ## Pseudocode ``` interface Database: method query(sql): string class RealDatabase implements Database: constructor(): output "Connecting to database... (expensive)" method query(sql): return "Result of: " + sql // Virtual proxy — lazy init class LazyDatabaseProxy implements Database: field real: Database = null method query(sql): if real is null: real = new RealDatabase() return real.query(sql) // Protection proxy class ProtectedDatabaseProxy implements Database: field real: Database field user_role: string constructor(real, role): this.real = real; this.user_role = role method query(sql): if user_role != "admin": output "ACCESS DENIED for role: " + user_role return null return real.query(sql) // Logging proxy class LoggingDatabaseProxy implements Database: field real: Database constructor(real): this.real = real method query(sql): output "[LOG] query: " + sql result = real.query(sql) output "[LOG] result: " + result return result ``` ## Python ```python from abc import ABC, abstractmethod class Database(ABC): """Service interface.""" @abstractmethod def query(self, sql: str) -> str: ... class RealDatabase(Database): """Expensive real service.""" def __init__(self) -> None: print(" [RealDB] Connecting to database... (expensive)") def query(self, sql: str) -> str: return f"Result of: {sql}" # --- Virtual Proxy (lazy loading) --- class LazyDatabaseProxy(Database): """Delays creation of RealDatabase until first use.""" def __init__(self) -> None: self._real: RealDatabase | None = None def query(self, sql: str) -> str: if self._real is None: print(" [LazyProxy] First access — initializing real database") self._real = RealDatabase() return self._real.query(sql) # --- Protection Proxy --- class ProtectedDatabaseProxy(Database): """Checks user role before allowing queries.""" def __init__(self, real: Database, user_role: str) -> None: self._real = real self._role = user_role def query(self, sql: str) -> str: if self._role != "admin": print(f" [ProtectionProxy] ACCESS DENIED for role '{self._role}'") return "ACCESS DENIED" return self._real.query(sql) # --- Logging Proxy --- class LoggingDatabaseProxy(Database): """Logs every query transparently.""" def __init__(self, real: Database) -> None: self._real = real def query(self, sql: str) -> str: print(f" [LogProxy] Executing: {sql}") result = self._real.query(sql) print(f" [LogProxy] Got: {result}") return result if __name__ == "__main__": print("=== Virtual Proxy (lazy loading) ===") lazy_db = LazyDatabaseProxy() print("Proxy created, no DB connection yet.") print(lazy_db.query("SELECT * FROM users")) print(lazy_db.query("SELECT * FROM orders")) print("\n=== Protection Proxy ===") real_db = RealDatabase() admin_db = ProtectedDatabaseProxy(real_db, "admin") guest_db = ProtectedDatabaseProxy(real_db, "guest") print(admin_db.query("SELECT * FROM secrets")) print(guest_db.query("SELECT * FROM secrets")) print("\n=== Logging Proxy ===") logged_db = LoggingDatabaseProxy(real_db) print(logged_db.query("SELECT count(*) FROM users")) ``` **Output:** ``` === Virtual Proxy (lazy loading) === Proxy created, no DB connection yet. [LazyProxy] First access — initializing real database [RealDB] Connecting to database... (expensive) Result of: SELECT * FROM users Result of: SELECT * FROM orders === Protection Proxy === [RealDB] Connecting to database... (expensive) Result of: SELECT * FROM secrets [ProtectionProxy] ACCESS DENIED for role 'guest' ACCESS DENIED === Logging Proxy === [LogProxy] Executing: SELECT count(*) FROM users [LogProxy] Got: Result of: SELECT count(*) FROM users Result of: SELECT count(*) FROM users ``` ## Go ```go package main import "fmt" // Database is the service interface. type Database interface { Query(sql string) string } // RealDatabase is the expensive real service. type RealDatabase struct{} func NewRealDatabase() *RealDatabase { fmt.Println(" [RealDB] Connecting to database... (expensive)") return &RealDatabase{} } func (db *RealDatabase) Query(sql string) string { return "Result of: " + sql } // --- Virtual Proxy --- type LazyDatabaseProxy struct { real *RealDatabase } func (p *LazyDatabaseProxy) Query(sql string) string { if p.real == nil { fmt.Println(" [LazyProxy] First access — initializing real database") p.real = NewRealDatabase() } return p.real.Query(sql) } // --- Protection Proxy --- type ProtectedDatabaseProxy struct { real Database role string } func NewProtectedProxy(real Database, role string) *ProtectedDatabaseProxy { return &ProtectedDatabaseProxy{real: real, role: role} } func (p *ProtectedDatabaseProxy) Query(sql string) string { if p.role != "admin" { fmt.Printf(" [ProtectionProxy] ACCESS DENIED for role '%s'\n", p.role) return "ACCESS DENIED" } return p.real.Query(sql) } // --- Logging Proxy --- type LoggingDatabaseProxy struct { real Database } func NewLoggingProxy(real Database) *LoggingDatabaseProxy { return &LoggingDatabaseProxy{real: real} } func (p *LoggingDatabaseProxy) Query(sql string) string { fmt.Printf(" [LogProxy] Executing: %s\n", sql) result := p.real.Query(sql) fmt.Printf(" [LogProxy] Got: %s\n", result) return result } func main() { fmt.Println("=== Virtual Proxy (lazy loading) ===") lazyDB := &LazyDatabaseProxy{} fmt.Println("Proxy created, no DB connection yet.") fmt.Println(lazyDB.Query("SELECT * FROM users")) fmt.Println(lazyDB.Query("SELECT * FROM orders")) fmt.Println("\n=== Protection Proxy ===") realDB := NewRealDatabase() adminDB := NewProtectedProxy(realDB, "admin") guestDB := NewProtectedProxy(realDB, "guest") fmt.Println(adminDB.Query("SELECT * FROM secrets")) fmt.Println(guestDB.Query("SELECT * FROM secrets")) fmt.Println("\n=== Logging Proxy ===") loggedDB := NewLoggingProxy(realDB) fmt.Println(loggedDB.Query("SELECT count(*) FROM users")) } ``` **Output:** ``` === Virtual Proxy (lazy loading) === Proxy created, no DB connection yet. [LazyProxy] First access — initializing real database [RealDB] Connecting to database... (expensive) Result of: SELECT * FROM users Result of: SELECT * FROM orders === Protection Proxy === [RealDB] Connecting to database... (expensive) Result of: SELECT * FROM secrets [ProtectionProxy] ACCESS DENIED for role 'guest' ACCESS DENIED === Logging Proxy === [LogProxy] Executing: SELECT count(*) FROM users [LogProxy] Got: Result of: SELECT count(*) FROM users Result of: SELECT count(*) FROM users ``` ## JavaScript ```javascript class RealDatabase { constructor() { console.log(" [RealDB] Connecting to database... (expensive)"); } query(sql) { return `Result of: ${sql}`; } } // --- Virtual Proxy (lazy loading) --- class LazyDatabaseProxy { constructor() { this._real = null; } query(sql) { if (this._real === null) { console.log( " [LazyProxy] First access — initializing real database" ); this._real = new RealDatabase(); } return this._real.query(sql); } } // --- Protection Proxy --- class ProtectedDatabaseProxy { constructor(real, role) { this._real = real; this._role = role; } query(sql) { if (this._role !== "admin") { console.log( ` [ProtectionProxy] ACCESS DENIED for role '${this._role}'` ); return "ACCESS DENIED"; } return this._real.query(sql); } } // --- Logging Proxy --- class LoggingDatabaseProxy { constructor(real) { this._real = real; } query(sql) { console.log(` [LogProxy] Executing: ${sql}`); const result = this._real.query(sql); console.log(` [LogProxy] Got: ${result}`); return result; } } // --- Main --- console.log("=== Virtual Proxy (lazy loading) ==="); const lazyDB = new LazyDatabaseProxy(); console.log("Proxy created, no DB connection yet."); console.log(lazyDB.query("SELECT * FROM users")); console.log(lazyDB.query("SELECT * FROM orders")); console.log("\n=== Protection Proxy ==="); const realDB = new RealDatabase(); const adminDB = new ProtectedDatabaseProxy(realDB, "admin"); const guestDB = new ProtectedDatabaseProxy(realDB, "guest"); console.log(adminDB.query("SELECT * FROM secrets")); console.log(guestDB.query("SELECT * FROM secrets")); console.log("\n=== Logging Proxy ==="); const loggedDB = new LoggingDatabaseProxy(realDB); console.log(loggedDB.query("SELECT count(*) FROM users")); ``` **Output:** ``` === Virtual Proxy (lazy loading) === Proxy created, no DB connection yet. [LazyProxy] First access — initializing real database [RealDB] Connecting to database... (expensive) Result of: SELECT * FROM users Result of: SELECT * FROM orders === Protection Proxy === [RealDB] Connecting to database... (expensive) Result of: SELECT * FROM secrets [ProtectionProxy] ACCESS DENIED for role 'guest' ACCESS DENIED === Logging Proxy === [LogProxy] Executing: SELECT count(*) FROM users [LogProxy] Got: Result of: SELECT count(*) FROM users Result of: SELECT count(*) FROM users ``` ## Related Patterns - **Decorator** — Both wrap objects with the same interface. Proxy controls access; Decorator adds behavior. Combined: Decorator wraps a Proxy to add both behavior and access control. - **Adapter** — Adapter wraps to change the interface; Proxy wraps to control access while keeping the same interface. - **Facade** — Facade simplifies a subsystem interface; Proxy keeps the same interface but adds control. - **Memoization** — Caching Proxy implements memoization transparently. - **Guard Clause** — Protection Proxy implements Guard Clause logic at the wrapper level.