The Proxy design pattern can be used in multiple cases, mostly to encapsulate an instance of another object or to add auxiliary functionality. Proxy allows us to control access to the entire or partial interface of an object through another representative object (proxy - representative).
We've all certainly seen (or we will definitely see) the case where we want to control access to an object. Either for safety or to add functionality, improve the performance or to simplify debugging, etc. In all of these cases, we'll probably use this design pattern.
In order to fully understand where and when to use the Proxy design pattern, we'll explain some typical variants of it solving different situations. These variants are also listed in GoF.
If we want to communicate with an object but on another virtual machine, or on a completely different computer, we can use Remote Proxy. It's a representative of a remote object holding all the communication inside.
Remote Proxy represents an object located somewhere else. Its task is to communicate with this object so that users can communicate with the remote (original) object via Remote Proxy without having to worry about server communication (it's ensured by the Proxy object). At the same time, however, it must not hide communication. This is, for example, when the connection fails, it must throw the corresponding exception. The user must expect this and if necessary, handle it appropriately. The user can communicate with the remote proxy almost as with a regular object.
The implementation of Remote Proxy itself can be done in different ways. It's more complicated because we have to establish the already mentioned communication between these two objects. We also always need to expect possible communication failures with the remote object during implementation.
Proxy is often used as a Virtual Proxy. In our application, it can happen very often that we have an object which is difficult to create or manage, or eventually, it'd take a long time to load it all especially if we need only a part of it. Virtual Proxy tries to create an instance of the managed class not before it's really needed.
In this case, Virtual Proxy also takes care of another object (represents it). However, it waits with creating the object until the last moment (if we need to create it at all) and only pretends the behavior of the original object. I.e. on the outside, it can have the same interface but it only delegates the functionality to the original object or adds some additional functionality and accesses the original object or creates it only if it's really necessary. Typically, this may be an image. It may be necessary to render it just once or not at all. However, our application will ask for its dimensions or other metadata (even when it doesn't render it later). It'd be inefficient to load the picture into memory when we don't even use it. Another very common example is more complex operations. These are typically objects loading data from the database. The application might not even use all the data from the storage. We can define a Proxy class (Virtual Proxy) to load and create the relevant object only when it's actually needed, load just the part we are working with, write its results into the cache, so we won't have to reload it over and over again and so on.
In the application design, we may sometimes encounter the case where using the entire API (public interface) of an object could have a fatal impact. For example, this can be a situation where each object has different access rights to our key object, or we just need to simplify the object's interface so that someone who doesn't know our system wouldn't used it in a wrong, leading to fatal consequences in some cases.
In both of the above-mentioned cases, we define a new Proxy class that manages an instance of the object, but on the outside, offers only a subset of the methods of the object it represents. We can then control which target object methods the user will have access to and which not. In some cases, when we just need to restrict the interface, it can be replaced with a simplified interface, which is more effective, but less secure. In this case, the user still has access to the entire object, just under a simplified interface. They can easily find out what the object it is.
If we want to add some other actions to the communication with the object to increase the efficiency and speed up the entire application, the Smart reference is the design pattern we are looking for.
It's typically used to store a persistent object into memory. We can then cleverly preserve such an object between the individual requests to our application to speed it up. Such a smart reference can e.g. set the limit after which the connection to the remote server is terminated or erase itself from the memory once the request is complete. If no client is connected within the given time limit, the smart reference will simply reload into memory when the next connection is made or reopen the connection to the remote server. This is used, for example, for keeping the connection with the database or another remote server alive.
Every virtual proxy that we explained above also behaves as a smart reference. It also has to decide when it accesses the original object and when it'll use another help instead (for example, read values from the cache, retrieve only metadata instead of loading the entire media, etc.)
The Proxy design pattern belongs among the design patterns of GoF. It's, therefore, the "real" design pattern. It's mostly used to encapsulate the application, but it can also be used to improve functionality (e.g. to speed up the application, etc.). We distinguish four proxies: Remote Proxy, Virtual Proxy, Protected Proxy, and Smart Reference.