🙋🏻♀️ What is Proxy Pattern?
The Proxy Pattern is a structural design pattern that provides a placeholder for another object. It allows you to control access to the original object so that it can enhance the object’s functionality, provide lazy initialization, implement access control, or perform caching.
By definition, a proxy
means a middleman or a substitute to represent someone else. The proxy
acts as an intermediary between the client and the real object, intercepting requests and managing their execution.
✨ Simple TypeScript Example
Let’s make a HttpServiceProxy
that improves performance and reduces network traffic.
First, define Subject
interface, which represents the common interface for both RealHttpService
and HttpServiceProxy
.
type MethodType = Request['method']
type UrlType = Request['url']
type BodyType = Request['body']
interface Subject {
request(method: MethodType, url: UrlType, body?: BodyType): Promise<any>
}
Then, RealHttpService
represents the real implementation of the HTTP service. The request
method sends the HTTP request using the Fetch API, receives the response, and converts it to JSON before returning the data.
class RealHttpService implements Subject {
async request(method: MethodType, url: UrlType, body?: BodyType) {
return fetch(url, {
method,
body,
}).then((response) => response.json())
}
}
Now, HttpServiceProxy
, which implements the Subject
interface as well, acts as a proxy for the RealHttpService
adding caching functionality.
type CacheKey = `${MethodType}-${UrlType}`
class HttpServiceProxy implements Subject {
private readonly realHttpService: RealHttpService
private readonly cache: Map<CacheKey, any> = new Map()
constructor() {
this.realHttpService = new RealHttpService()
}
async request(method: MethodType, url: UrlType, body?: BodyType) {
const cacheKey: CacheKey = `${method}-${url}`
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)
}
const response = await this.realHttpService.request(method, url, body)
this.cache.set(cacheKey, response) // for future use
return response
}
}
If the client call asyncGetCake
the first time with await asyncGetCake()
, it makes a request to the server through the httpService
proxy, which in turn makes the request using the realHttpService
.
// Client Code
const httpService = new HttpServiceProxy()
async function asyncGetCake() {
await httpService.request('GET', 'https://exmaple.com/cake').then((response) => {
console.log(response)
})
}
await asyncGetCake() // This will make a request to the server
await asyncGetCake() // This will use the cached response
When the client call asyncGetCake
the second time, it uses the cached response stored in the cache
of the HttpServiceProxy
. As a result, it doesn't make a request to the server again.
🧑🏻💻 Use it or Avoid it
When to use it
Common use cases are:
- Remote Proxy: to provide a local representation of a remote object
- Virtual Proxy: to create expensive objects on demand(lazy initialization), optimizing resource usage and initialization time.
- Protection Proxy: to control access to sensitive objects or operations by implementing access control mechanisms.
- Caching Proxy: to cache the results of expensive operations.
- Logging Proxy: to add logging or auditing functionality to the operations of an object.
When to avoid it
- If the object being proxied is not complex nor have to add additional functionality or control over its operations.
- If frequent object creation or method calls are involved.