Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object to control access to it.
Why would you control the access to an object? Here is an example: you have a powerful object that loves to consume vast amounts of system resources. You need it from time to time, but not always.
Thus, the object could probably be created not at the program start, but rather when it is actually needed. Each client of this object would need to have some sort of deferred initialization code. Obviously, it leads to a lot of duplication.
In an ideal world, we would put this code directly into the object's class, but that is not always possible. For instance, the class may reside in a closed 3rd-party library.
The Proxy pattern suggests creating a substitute class that has the same interface as an original service object. Upon receiving the request from a client, the proxy object creates an instance of a service object and delegate it all real work.
But what is the benefit? You could put some code into a proxy object that would be executed just before (or after) calling the same method in a real service object. And thanks to the same interface that proxy shares with the service object, it could be passed to any code that accepts service objects.
A check is a document that orders a bank to pay a specific amount of money from a person's account to the person in whose name the cheque has been issued. Both check and cash have a common interface: they can be used as payment. Thus, a check is a proxy for a bunch of cash.
From a consumer standpoint, checks are great because there is no need to carry loads of cash around. For shop owners, they are also fine, since can be converted to cash at the nearest bank.
Service interface declares the common interface for both Service and Proxy.
Service is a class that provides useful business logic.
Proxy has a field that stores reference to a Service object. Methods of the Proxy do some intermediate work and, most of the time pass the requests along to the same method of the Service object.
In most cases, Proxies manage the life cycle of their Service objects.
Clients should work with both Services and Proxies through a common Service interface. It allows passing Proxy objects into any code that expects a Service object.
In this example, the Proxy pattern helps to implement the lazy initialization and caching to an inefficient 3rd-party Youtube integration library.
The original video downloader class would download a video even if it has been already downloaded before. The improved proxy class downloads the same video just once using the original downloader, but then caches it and returns the cached file on each subsequent request.
Lazy initialization (virtual proxy). When you have a heavyweight object that loads data from a filesystem, network or database.
Instead of loading data at the application start, one could delay the object's initialization to a time when it is needed.
Access control (protection proxy). When a program has different user types and you want to protect an object from the unauthorized usage. For instance, when objects are crucial parts of an operating system and programs (including malicious ones) are their clients.
Proxy can check the client's credentials on each request and pass the request to the service object only if the access is granted.
Local execution of a remote service (remote proxy). When a real service object is located on a remote server.
In this case proxy passes client request to a remote service object over the network, handling all the network transfer details.
Caching objects ("smart" reference). When you need to cache the results of client requests and manage their life cycle (when the results are heavyweight).
Proxy can count the number of reference to a service object or cached results. When all references released, the proxy can destroy the object it tracks (for instance, terminate the database connection).
Proxy can also track whether the client changed the service object. It allows reusing unchanged object and saves the system resources.
Request logging (logging proxy). When you need to keep a history of requests to a service object.
Proxy can log each request before passing it to a service object.
How to Implement
Extract the interface from a service class that would make proxy and service objects interchangeable. You may need to make proxy a subclass of service if the code of service class is closed.
Create a proxy class. It should have a field for storing a reference to a service object. In most cases, proxy creates service object on its own. In rare occasions, service is passed to proxy via constructor by the client.
Implement the proxy methods according to its purpose. In most cases, after doing some work, the proxy should delegate the work to a service object.
Think of introducing a factory object that would decide what object client needs, proxy or real service. On the other hand, this logic can live inside a creational method in of a proxy class.
Consider adding lazy initialization for a service object. It is very useful for heavyweight objects.
Pros and Cons
- Controls access to objects without clients noticing.
- Works even when service object is not ready.
- Manages the lifecycle of a service object even when clients do not care.
- Delays the response.
Relations with Other Patterns
Facade is similar to Proxy in that it buffers a complex entity and also initializes it. Unlike the Facade, the Proxy pattern has the same interface as its service object, which makes them interchangeable.
Decorator and Proxy have similar structures but different purposes. Both patterns built on the composition principle of delegating work to other object. However, the Proxy manages the life cycle of its service object by itself, whereas Decorator structure is controlled by client.