Facebook Pixel

Proxy

What a proxy is

A proxy is an object that stands in for another object and controls access to it. Both the proxy and the real object implement the same interface, so callers interact with them interchangeably without knowing which one they hold. The proxy intercepts calls, does something — delays creation, checks a permission, forwards to a remote machine, or returns a cached result — and then optionally delegates to the real object.


The shared Subject interface is the structural foundation. Without it, a caller cannot use the proxy in place of the real object. With it, the proxy is invisible to callers — they hold a Subject reference and call request(), regardless of whether a proxy or the real object answers.

The problem it fixes

Before introducing a proxy, the logic that should belong to the access layer tends to scatter across callers. Each call site re-implements lazy initialisation, permission checks, or result caching independently. When the rule changes — for example, a new role needs access — every call site must be updated.

1# Bad: every caller handles lazy init, auth, and caching independently.
2class ReportService:
3    def generate(self, report_id: str) -> str:
4        return f"<report:{report_id}>"
5
6class DashboardPage:
7    def __init__(self, user_role: str) -> None:
8        self._role = user_role
9        self._service: ReportService | None = None  # lazy init duplicated everywhere
10        self._cache: dict[str, str] = {}
11
12    def load_report(self, report_id: str) -> str:
13        if self._role not in ("admin", "analyst"):          # auth duplicated
14            raise PermissionError("access denied")
15        if report_id in self._cache:                        # caching duplicated
16            return self._cache[report_id]
17        if self._service is None:                           # init duplicated
18            self._service = ReportService()
19        result = self._service.generate(report_id)
20        self._cache[report_id] = result
21        return result
22
23class ExportPage:
24    def __init__(self, user_role: str) -> None:
25        self._role = user_role
26        self._service: ReportService | None = None  # same pattern again
27        self._cache: dict[str, str] = {}
28
29    def export(self, report_id: str) -> str:
30        if self._role not in ("admin", "analyst"):
31            raise PermissionError("access denied")
32        if report_id in self._cache:
33            return self._cache[report_id]
34        if self._service is None:
35            self._service = ReportService()
36        return self._service.generate(report_id)

Good: a proxy centralises control

A proxy implements the same interface as ReportService and handles the cross-cutting logic in one place. Both DashboardPage and ExportPage hold a reference to ReportServiceProxy typed as the shared interface. Neither caller contains any auth, caching, or initialisation code.

1from abc import ABC, abstractmethod
2
3class IReportService(ABC):
4    @abstractmethod
5    def generate(self, report_id: str) -> str: ...
6
7
8class ReportService(IReportService):
9    def generate(self, report_id: str) -> str:
10        return f"<report:{report_id}>"
11
12
13class ReportServiceProxy(IReportService):
14    """Combines protection + caching + lazy init behind the shared interface."""
15
16    def __init__(self, user_role: str) -> None:
17        self._role = user_role
18        self._real: ReportService | None = None
19        self._cache: dict[str, str] = {}
20
21    def generate(self, report_id: str) -> str:
22        if self._role not in ("admin", "analyst"):
23            raise PermissionError("access denied")
24        if report_id in self._cache:
25            return self._cache[report_id]
26        if self._real is None:
27            self._real = ReportService()
28        result = self._real.generate(report_id)
29        self._cache[report_id] = result
30        return result
31
32
33# Callers never import ReportService directly.
34def make_report_service(role: str) -> IReportService:
35    return ReportServiceProxy(role)
36
37svc = make_report_service("analyst")
38print(svc.generate("q4-2024"))  # "<report:q4-2024>"
39print(svc.generate("q4-2024"))  # returned from cache
Invest in Yourself
Your new job is waiting. 83% of people that complete the program get a job offer. Unlock unlimited access to all content and features.
Go Pro