Design Patterns · Structural Patterns
Proxy
Provide a stand-in with the service's interface. Control initialization, remote access, permissions, or caching at that boundary.
This lesson follows Flyweight and closes the structural patterns.
Proxy stands in for another object and controls access to it through the same contract. The client can use the proxy where it expects the service. That convenience adds hidden work and lifecycle decisions.
Delay an object that is rarely used
An editor can preview a document. Its renderer has expensive font initialization. Most sessions never open preview. Creating the renderer at startup charges every session for a feature only some use.
interface Preview { render(document: string): string }
class DocumentRenderer implements Preview {
constructor() { console.log("Initialize renderer fonts"); }
render(document: string) { return "Preview: " + document; }
}
class LazyPreview implements Preview {
private service: Preview | undefined;
constructor(private create: () => Preview) {}
render(document: string): string {
this.service ??= this.create();
return this.service.render(document);
}
}
const preview: Preview = new LazyPreview(() => new DocumentRenderer());
// No renderer yet.
preview.render("Draft A"); // initialize, then render
preview.render("Draft B"); // reuse, then render
Step through
Trace the service lifetime
Create the proxy
proxy count: 1
renderer count: 0
Render for the first time
create() → DocumentRenderer
store service reference
delegate render('Draft A')
Render again
reuse the service
delegate render('Draft B')
renderer count: 1
Match the proxy to the access problem
Try it
Choose the work at the boundary
virtual proxy → initialize on first use
Define who owns disposal and what happens after initialization fails.
remote proxy → serialize, send, decode
Network delay, timeouts, and partial failure still exist. Use a contract that exposes asynchronous failure rather than pretending the operation is an ordinary local call.
protection proxy → authorize, then delegate
Enforce the access rule at a boundary the caller cannot bypass. A client-side wrapper alone cannot protect a server resource.
cache proxy → lookup, fetch on miss, store
Define the key, freshness, invalidation, and result ownership. An old response is still old when returned through a proxy.
Other proxies log calls or manage references to an expensive resource. The common idea is a substitute that mediates access. The mechanism depends on the problem.
The same interface cannot hide a broken promise
A caching proxy for a current-balance API must not silently return a week-old value. A remote proxy needs to preserve failure information. Interchangeability covers behavior as well as method names.
Map the roles
| Role | In this example | Job |
|---|---|---|
| Service interface | Preview | Declares the interface of the service. The proxy must follow it to pass as the service. |
| Service | DocumentRenderer | A class that provides useful business logic |
| Proxy | LazyPreview | Holds a reference to the service. Does its own job, such as lazy initialization, logging, access control, or caching, then passes the request to the service. Usually manages the service's whole life cycle. |
| Client | the editor | Works with services and proxies through the same interface, so a proxy fits anywhere a service is expected |
Check yourself
Who usually creates the real DocumentRenderer in this design?
Correct. A proxy usually manages its service's life cycle. Occasionally a client passes the service in through the constructor.
Not quite. That would charge every session the startup cost the proxy exists to avoid.
Reach for it when access to an object needs a gatekeeper
The pattern has many uses. These are the most common:
- lazy initialization (virtual proxy): a heavyweight service object wastes resources by staying alive, but you need it only sometimes. Create it when it is first needed.
- access control (protection proxy): only specific clients may use the service. The proxy passes a request on only if the client's credentials match.
- local execution of a remote service (remote proxy): the service lives on another server. The proxy sends requests over the network and handles the networking details.
- logging requests (logging proxy): you need a history of requests to the service. The proxy logs each request before passing it on.
- caching results (caching proxy): you need to cache client requests and manage the cache's life cycle, especially for large results. The proxy can key the cache by request parameters.
- smart reference: you need to dismiss a heavyweight object once no client uses it. The proxy tracks the clients that hold the service or its results, checks whether they are still active, and releases the service when none are left. It can also track whether a client modified the service, so unmodified objects can be reused.
Check yourself
An admin-only export service must reject ordinary users without touching the export code. Which kind of proxy?
Not quite. Virtual proxies delay creation. They do not decide who may call the service.
Correct. Access control is a classic proxy job. Enforce it where callers cannot bypass it.
Implement it in five steps
Refactor existing code toward the pattern in this order:
- If there is no service interface yet, create one so the proxy and the service are interchangeable. Extracting an interface is not always possible, because every client would have to change. The fallback is to make the proxy a subclass of the service, so it inherits the service's interface.
- Create the proxy class with a field that references the service. Usually the proxy creates and manages the service's whole life cycle. Occasionally the client passes the service in through the constructor.
- Implement the proxy methods for your purpose. In most cases the proxy does its work, then delegates to the service.
- Consider a creation method that decides whether a client gets a proxy or the real service. It can be a simple static method on the proxy or a full factory method.
- Consider lazy initialization for the service object.
Check yourself
You cannot extract an interface from a third-party service class, because its clients cannot change. What is the fallback?
Not quite. The subclass route still works whenever the class is not final.
Correct. Subclassing keeps the proxy substitutable without touching clients.
Name what it costs
| You gain | You pay |
|---|---|
| Control the service object without clients knowing | More classes, so the code gets more complicated |
| Manage the service's life cycle when clients do not care about it | Responses from the service might be delayed |
| The proxy works even if the service is not ready or not available | Caching and network behavior can change what callers observe |
| New proxies without changing the service or clients (open/closed) | Initialization failures and concurrent first calls need explicit rules |
Check yourself
A caching proxy speeds up a current-balance API by returning week-old values. What went wrong?
Correct. Interchangeability includes behavior. Define cache keys and freshness before adding the proxy.
Not quite. Callers trust the service contract, and the proxy must keep it.
Do not confuse it with its neighbors
| Pattern | How it relates |
|---|---|
| Adapter | Adapter gives the wrapped object a different interface. Proxy gives it the same interface. Decorator gives it an enhanced interface. |
| Decorator | Similar structure, both based on delegation. A proxy usually manages its service's life cycle on its own. A decorator's composition is always controlled by the client. |
| Facade | Both buffer a complex entity and can initialize it on their own. Unlike a facade, a proxy has the same interface as its service, so the two are interchangeable. |
Check yourself
A wrapper with the same interface adds optional behavior, and the client chooses and stacks the layers. Proxy or Decorator?
Correct. A proxy normally controls its own service. Client-built stacks point to Decorator.
Not quite. Both keep the interface. Who controls composition and lifetime tells them apart.
Retrieve and apply
Check yourself
A lazy proxy defers renderer creation. Which cost moves rather than disappears?
Correct. Sessions that never render avoid the cost. The first session call that does render still pays for initialization.
Not quite. The real service still has to be constructed before it can perform the operation.
Name one proxy policy and the service promise it must preserve. Continue to Chain of Responsibility to route a request through handlers.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Proxy, printed pages 219–230. Explanations, examples, and exercises are adapted for this course.