December 14th, 2019
Have you heard of the "Vote by proxy" phrase? In the Finance world, shareholders may choose to give management or some other group the ability to vote for them, allowing them to vote by proxy. The proxy pattern in software design follows the same basic idea. When you don't want to expose a certain resource, you can use the proxy pattern to control, manage, or protect the object.
You may sometimes come across the proxy pattern disguised or named something like a surrogate, wrapper, or a handler, but if the intent is to have another object in place of the original object, then chances are, you are dealing with a proxy.
The proxy can give an object additional security and functionality. Say for an example, we have an ATM, and we want to limit access to that ATM, then we could use an ATM Proxy that talks with the ATM, then all other resources could talk with the proxy. This way, there is an object in between the ATM and the objects in a system. By building the additional layer between the ATM, or object, you are able to mask some of the functionality, and other system components from directly accessing your ATM.
The proxy can also give an object additional functionality, especially to save on expensive functions. For example, suppose that connecting to a database is an expensive operation. Then the proxy can start up the database on a read/write operation, instead of connecting initially, but because the proxy has the same interface, the code that interfaces with the database, doesn’t need to know about these kinds of details.
As with any abstraction, this adds another layer of complexity and increases the amount of code to maintain. But this may be a necessary evil when you are trying to secure or control certain resources or objects in your code.
For our example, we are going to use an Atm
class. Let's suppose though, that for security, we are going to only limit other objects in talking with the proxy, instead of talking directly with the Atm
class.
export class Atm implements IAtm {
public deposit(amount: number): void {
console.log(`Atm: Depositing $${amount} to your bank account.`);
}
public withdraw(amount: number): void {
console.log(`Atm: Withdrawing $${amount} from your bank account.`);
}
}
Let’s also set up an interface for our ATM and a Logger that we can use.
export interface IAtm {
deposit(amount: number): void;
withdraw(amount: number): void;
}
export class Logger {
public static log(message: string) {
console.log(`Logger: ${message}`);
}
}
Now we can add our AtmProxy
which will implement our IAtm
interface.
export class AtmProxy implements IAtm {
private atm: Atm;
public deposit(amount: number): void {
console.log(`Proxy: Trying to deposit $${amount} to your bank account.`);
if (this.atm === null || this.atm === undefined) {
this.connectToAtm();
}
this.atm.deposit(amount);
Logger.log('Successful deposit!');
}
public withdraw(amount: number): void {
console.log(`Proxy: Trying to withdraw $${amount} from your bank account.`);
if (this.atm === null || this.atm === undefined) {
this.connectToAtm();
}
this.atm.withdraw(amount);
Logger.log('Successful withdrawal!');
}
private connectToAtm() {
console.log(`Proxy: Reconnecting to Atm`);
this.atm = new Atm();
}
}
Notice the deposit()
method. We are able to extend some functionality because we are using our proxy. This way, if our connection was lost, then we can reconnect because of our proxy! Pretty slick, eh!
Check out the full demo here: The Proxy. To try it out, simply run the typescript compiler tsc
then run node proxy.js
from the console.
The proxy is an important pattern in any software developer’s toolkit. It allows you to secure an object through a layer of abstraction and manage the object interactions, especially if you don't own the code for the object. Lastly, the proxy can protect specific objects from malicious or resource draining functions.
Want to be notified about new posts?
Then sign up to get posts directly to your email. You can opt-out at any time.