From grimoire
Controls access to an object for lazy initialization, access control, logging, caching, or remote access without changing its interface. Based on the Gang of Four Proxy pattern.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-proxy-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Provide a surrogate or placeholder for another object to control access to it.
Provide a surrogate or placeholder for another object to control access to it.
Adopted by: Spring Framework's AOP (every @Transactional and @Cacheable bean
is a proxy — used in the majority of Spring applications), Java RMI (remote proxy for
distributed objects — Java's original distributed computing model), Hibernate's lazy
loading (collection proxies — default in every Hibernate application), and Python's
unittest.mock (a virtual proxy over any object).
Impact: Spring's transaction proxy is cited in the Spring documentation as the
reason @Transactional requires no manual connection management — 80%+ reduction in
transaction-handling boilerplate (Spring documentation). Hibernate's virtual proxy for
lazy loading avoids loading entire object graphs; without it, a single entity load
triggers cascading database queries.
Why best: Adding cross-cutting concerns (logging, auth, caching) directly to the
subject class violates SRP — the class now handles both its own logic and the concern.
A proxy keeps the subject focused while the proxy layer intercepts access.
Sources: Gamma et al. (1994) pp. 207–217; Spring AOP documentation; Hibernate ORM documentation (proxy-based lazy loading)
from abc import ABC, abstractmethod
class ImageLoader(ABC):
@abstractmethod
def load(self, path: str) -> bytes: ...
@abstractmethod
def display(self) -> None: ...
class RealImageLoader(ImageLoader):
def __init__(self, path: str):
self._path = path
self._data: bytes | None = None
def load(self, path: str) -> bytes:
print(f"[IO] Loading {path} from disk")
with open(path, "rb") as f:
return f.read()
def display(self) -> None:
if self._data:
print(f"Displaying {len(self._data)} bytes")
Virtual proxy (lazy initialization):
class LazyImageProxy(ImageLoader):
def __init__(self, path: str):
self._path = path
self._real: RealImageLoader | None = None
def _ensure_loaded(self):
if self._real is None:
self._real = RealImageLoader(self._path)
self._real._data = self._real.load(self._path)
def load(self, path: str) -> bytes:
self._ensure_loaded()
return self._real._data
def display(self) -> None:
self._ensure_loaded()
self._real.display()
Protection proxy (access control):
class AuthImageProxy(ImageLoader):
def __init__(self, real: ImageLoader, user_role: str):
self._real = real
self._role = user_role
def load(self, path: str) -> bytes:
if self._role != "admin":
raise PermissionError("only admins can load raw images")
return self._real.load(path)
def display(self) -> None:
self._real.display()
loader: ImageLoader = LazyImageProxy("/images/banner.png")
# File not loaded yet
loader.display() # now loaded — only when needed
| Type | Purpose | Example |
|---|---|---|
| Virtual | Lazy initialization | Hibernate lazy collections |
| Remote | Local stand-in for remote object | Java RMI stub |
| Protection | Access control | Auth proxy |
| Caching | Memoize expensive calls | HTTP cache proxy |
| Logging | Audit access | @Transactional logging |
Proxy that changes the interface. A proxy must implement exactly the same interface as the subject. If it changes method signatures, it's a Decorator or Adapter, not a Proxy.
Forgetting thread safety in virtual proxies. The "check then initialize" in _ensure_loaded() is a race condition. Use a lock or functools.cached_property (Python) for thread-safe lazy initialization.
Using Proxy when Decorator is correct. Decorator adds behavior dynamically at runtime for an object known to the client. Proxy controls access to an object the client typically doesn't hold directly. The distinction matters for testability.
npx claudepluginhub jeffreytse/grimoire --plugin grimoireImplements virtual, protection, logging, and caching proxy patterns to control access to objects. Provides TypeScript examples for caching repositories and authorization proxies.
Generates PHP 8.4 Proxy pattern implementations for lazy loading, caching, access control, and logging. Includes subject interfaces, proxies, and unit tests in domain-driven project paths.
Ensures a class has exactly one instance with a global access point. Useful for configuration stores, connection pools, and loggers where multiple instances would cause resource conflicts.