Proxy is a structural design pattern that let you provide a substitute or placeholder for another object to controls access to it.
Why would you control the access to an object? Here's 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's 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's 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 all real work to it.
But what's 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's 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 defines 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.
// The interface of a remote service. interface ThirdPartyYoutubeLib is method listVideos() method getVideoInfo(id) method downloadVideo(id) // Concrete implementation of a service connector. Methods of this class can // request various info from youtube. Request speed depends on a user's internet // connection as wells as Youtube's. Application will slow down if a lot of // requests will be fired at the same time, even if they are requesting the // same info. class ThirdPartyYoutubeClass is method listVideos() is Send API request to Youtube. method getVideoInfo(id) is Get a meta information about some video. method downloadVideo(id) is Download video file from Youtube. // On the other hand, to save some bandwidth, we can cache request results and // keep them for some time. But it might be impossible to put such code directly // to the service class: it could be provided by third party library or/and // defined as final. That is why we put the caching code to a new proxy class // which implements the same interface as a service class. It is going to // delegate to the service object only when the real requests have to be sent. class CachedYoutubeClass implements ThirdPartyYoutubeLib is field youtubeService: ThirdPartyYoutubeClass field listCache, videoCache field needReset constructor YoutubeCache() is youtubeService = new ThirdPartyYoutubeClass() method listVideos() is if (listCache == null && !needReset) listCache = youtubeService.listVideos() return listCache method getVideoInfo(id) is if (videoCache == null && !needReset) videoCache = youtubeService.getVideoInfo(id) return videoCache method downloadVideo(id is if (!downloadExists(id) && !needReset) youtubeService.listVideos() // The GUI class, which used to work with a service object stays unchanged. But // only as long as it works with the service object through an interface. We can // safely pass here a proxy object instead of a real service object since both // of them implement the same interface. class YoutubeManager is field service: ThirdPartyYoutubeLib constructor YoutubeManager(service: ThirdPartyYoutubeClass) is this.service = service method renderVideoPage() is info = service.getVideoInfo() Render the video page. method renderListPanel() is list = service.listVideos() Render the list of video thumbnails. method reactOnUserInput() is renderVideoPage() renderListPanel() // Application can configure proxies on the fly. class Application is method init() is youtubeLib = new CachedYoutubeClass(); manager = new YoutubeManager(youtubeLib) manager.reactOnUserInput()
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's 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.