Easy patterns: Proxy
This article is created in continuation of easy patterns series description and presents a structural pattern named a Proxy which provides a placeholder for another object to control access to it.
Please refer to the other pattern articles as well:
Creational patterns:
Structural patterns:
Proxy (this article)
Behavioral patterns:
The main essence
Proxy pattern is useful in cases where a more versatile and sophisticated reference to an object is needed than a simple pointer. Proxy forwards requests to the Subject when appropriate, depending on the kind of proxy.
Some general kinds of proxy are:
- a remote proxy — provides a local representation of some remote object (for example collects some data chunks before send it to the server to save to avoid extra load for a network)
- a virtual proxy — replaces expensive object with lightweight representation. Creates such expensive object only on demand (for example image placeholder. User scrolls to the appropriate place in the document and only in such case we should load quite heavy picture)
- a protection proxy — controls access to the original object (for example in cases with separate access rights to some functionality, like some content with authorization separation)
- a smart reference — controls whether Subject should be locked (due to edit process by some other user) or counts a number of clicks on a Subject, etc
This pattern includes two main roles:
- Proxy — maintains a reference that lets the proxy access to real subject, provides an interface identical to Subject’s for a legal substitution and controls an access to the substitued Subject.
- Subject —defines an object that the proxy represents.
This pattern also known as a Surrogate.
In JavaScript language there is a special class named Proxy.
const proxyObject = new Proxy(subject, handlerObject);
// subject - target object
// handlerObject - object with a traps (hooks) for a specific operations with a subject
Let’s describe some of traps here:
get(target, property, receiver)
— is triggered when specificproperty
is red from thetarget
object.receiver
in majority of cases is a Proxy itself.set(target, property, receiver)
— is triggered in case of writing to a Proxy.has(target, query)
— is triggered in case when property existance is checked in the target object. (operatorin
)
const subject = {
name: 'Pavel',
age: 20
};const proxyObject = new Proxy(subject, {
has(target, property) {
console.log(`${property} is accessed`);
return true;
}
});console.log('someNonExistingProp' in proxyObject); // true
deleteProperty
— is triggered ondelete
operation. Should return true if delete was successfulapply(target, thisArg, argsList)
— is triggered when proxy object is a function and right after calling it.thisArg
is a context of calling (this
), andargsList
is a list of an arguments passed to a functionconstruct(target, argsList)
— is triggered when instantiation process appear (withnew
operator).
All the list and more information about Proxy class implementation you can find here.
Example of use
Let’s create a coffee machine with internal state (isOn
) and two controls to turn it on and off. The responsibility of proxy would be to control the access to these methods — if machine is turned on already there is no need to turn it on again. The same for situation when machine is turned off. In such cases we want to show message to the user that machine is turned on or off already without any further action.
This was a simple example of the access proxy.
Profit
The Proxy pattern introduces a level of indirection when accessing an object.
This indirection has many uses:
- protection proxies allow control of accessing process
- smart references proxies allow collecting information about subject usage
- virtual proxies can perform variety of optimizations (like creating object on demand and avoid system overload)
- remote proxies can provide an access to a remote object from a different address space
Another useful approach is called copy-on-write optimization. It’s related to the creation on demand. Copying a large and complicated object can be an expensive operation and if copy is never modified it makes no use for such cost. Using such approach we ensure that copying process is used only if we need to return complicated object which is modified. If not, we should return just a link to existing one and increment a reference count to it.
Weak places
The Proxy pattern introduces a level of indirection when accessing an object.
This leads to creation of additional instances and in some simple cases could be an overhead.
Conclusion
The main collaboration for a Proxy pattern: it forwards requests to Subject when appropriate, depending on the kind of proxy.
There are several common use case for the Proxy pattern:
- remote proxy
- virtual proxy
- protection proxy
- smart reference proxy
Proxy object wraps a Subject itself. Generally, for the safety, you can even rewrite a link to an original Subject:
let payload = {
name: 'Pavel',
age: 20
};payload = new Proxy(payload, {
... // some proxy hooks
});// payload object here is already wrapped by proxy
There are some other related design patterns such as Adapter and Decorator.
Adapter provides a different interface to the object it adapts and Proxy provides the same interface as its subject. And Proxy pattern is used also for access protection which can refuse to perform an operation.
Decorator adds one or more responsibilities to an object, whereas a Proxy controls access to a wrapped object. However decorators can have similar implementation as proxies.
If you found this article helpful, please hit the 👏 button and feel free to comment below!