d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
12 KiB
12 KiB
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:
- Virtual proxy — Delay expensive object creation until actually needed (lazy loading).
- Protection proxy — Restrict access based on permissions.
- 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__, JSProxy) 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
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
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
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.