Фото від Clay Banks на Unsplash
Шаблон проектування Proxy є одним із структурних шаблонів (structural patterns), який надає об'єкт, що представляє інший об'єкт. Proxy контролює доступ до оригінального об'єкта, і таким чином може додавати додаткову поведінку або управляти доступом різними способами.
У цій статті ми заглибимося в шаблон Proxy в TypeScript та Node.js, розглянемо, як реалізувати цей шаблон, як він може бути корисним і коли його слід використовувати в реальних додатках.
Коли використовувати шаблон Proxy 🤔
Варто розглянути використання шаблону Proxy, коли:
- Лінива ініціалізація (Virtual Proxy): Коли об'єкт важко створити, і ви хочете відкласти його створення до того часу, поки він не буде справді потрібен.
- Контроль доступу (Protection Proxy): Коли потрібно забезпечити, щоб певні операції з об'єктом виконувались лише користувачами з відповідними правами.
- Віддалений доступ (Remote Proxy): Для управління доступом до сервісу або об'єкта, який знаходиться на іншій машині або сервісі (наприклад, в розподілених додатках).
- Кешування (Cache Proxy): Для кешування результатів дорогих викликів функцій, щоб уникнути повторних обчислень.
Реалізація шаблону Proxy в TypeScript 🛠️
Давайте розглянемо кілька практичних прикладів, де шаблон Proxy корисний в TypeScript та Node.js додатках.
Приклад 1: Віртуальний Proxy
Віртуальний Proxy використовується для відкладеного створення та ініціалізації ресурсу до того, як він справді стане необхідним (також відомий як лінива ініціалізація).
У цьому прикладі, ProxySubject
створить і отримає дані з RealSubject
тільки тоді, коли важкий об'єкт ще не було створено.
class RealSubject {
private heavyData: string;
constructor() {
console.log("RealSubject: Creating a heavy object...");
this.heavyData = "Expensive Data";
}
public fetchData(): string {
return this.heavyData;
}
}
class ProxySubject {
private realSubject: RealSubject | null = null;
public fetchData(): string {
if (!this.realSubject) {
this.realSubject = new RealSubject(); // Лінива ініціалізація
}
return this.realSubject.fetchData();
}
}
const proxy = new ProxySubject();
console.log("Data: " + proxy.fetchData());
Клас RealSubject
виконує операції, які витрачають багато ресурсів, такі як завантаження даних з бази даних або зовнішнього джерела. ProxySubject
виступає як проміжний об'єкт, реалізуючи лінивий ініціалізаційний підхід, створюючи RealSubject
лише тоді, коли це необхідно, і делегуючи виклики йому. Цей підхід оптимізує продуктивність у великих системах, гарантує, що важкі дані завантажуються лише тоді, коли це потрібно, і знижує зайве споживання ресурсів.
Приклад 2: Protection Proxy
Protection Proxy контролює доступ до об'єкта на основі прав доступу чи аутентифікації. Наприклад, лише авторизовані користувачі можуть виконувати певні операції.
Цей приклад демонструє, як шаблон Proxy може застосовувати політики безпеки в системі.
interface FileAccess {
deleteFile(): void;
}
class RealFileAccess implements FileAccess {
deleteFile(): void {
console.log("File deleted.");
}
}
class ProtectionProxy implements FileAccess {
constructor(private realAccess: RealFileAccess, private userRole: string) {}
deleteFile(): void {
if (this.userRole !== "admin") {
console.log("Access Denied: Only admins can delete files.");
return;
}
this.realAccess.deleteFile();
}
}
const adminAccess = new ProtectionProxy(new RealFileAccess(), "admin");
adminAccess.deleteFile(); // Дозволено
const userAccess = new ProtectionProxy(new RealFileAccess(), "guest");
userAccess.deleteFile(); // Доступ заборонено
Клас RealFileAccess
виконує чутливі операції, такі як видалення файлу, тоді як ProtectionProxy
виступає як шар безпеки, перевіряючи роль користувача перед наданням доступу.
Якщо користувач є адміністратором, дія продовжується; в іншому випадку доступ відмовляється, що забезпечує контрольований і безпечний доступ до сервісу.
Приклад 3: Віддалений Proxy
Віддалений Proxy надає доступ до об'єкта, що знаходиться на іншій машині або процесі.
interface UserService {
getUser(id: number): Promise;
}
class RemoteUserService implements UserService {
async getUser(id: number): Promise {
console.log(`Fetching user ${id} from remote server...`);
return new Promise((resolve) =>
setTimeout(() => resolve(`User ${id}: John Doe`), 2000)
);
}
}
class UserServiceProxy implements UserService {
private remoteService: RemoteUserService = new RemoteUserService();
async getUser(id: number): Promise {
console.log("Calling remote service...");
return this.remoteService.getUser(id);
}
}
const userService = new UserServiceProxy();
userService.getUser(1).then(console.log);
RealUserService
є віддаленим сервісом, який вимагає асинхронної комунікації, наприклад, HTTP-запитів або RPC. RemoteProxy
виступає як проміжний об'єкт, обробляючи взаємодії з віддаленим сервісом, абстрагуючи складнощі віддаленої комунікації від клієнтського коду.
Приклад 4: Cache Proxy
Cache Proxy зберігає результати дорогих операцій або віддалених викликів для повторного використання, що запобігає зайвій роботі.
class ExpensiveService {
public fetchData(): string {
console.log("Fetching data from database...");
return "Expensive Data";
}
}
class CacheProxy {
private expensiveService: ExpensiveService;
private cachedData: string | null = null;
constructor() {
this.expensiveService = new ExpensiveService();
}
public fetchData(): string {
if (this.cachedData === null) {
this.cachedData = this.expensiveService.fetchData(); // Зберігаємо результат для подальшого використання
}
return this.cachedData;
}
}
const proxy = new CacheProxy();
console.log(proxy.fetchData()); // Отримує з сервісу
console.log(proxy.fetchData()); // Повертає кешовані дані
ExpensiveService
імітує операцію, що вимагає багато ресурсів, наприклад, отримання даних з віддаленого сервера або виконання складних обчислень. CacheProxy
оптимізує продуктивність, зберігаючи результат першого виклику fetchData()
, гарантуючи, що наступні запити повертають кешовані дані замість повторного виклику дорогого сервісу. Цей підхід ідеально підходить для мінімізації зайвих операцій і підвищення ефективності.
Висновок 📣
Шаблон проектування Proxy в TypeScript та Node.js може значно підвищити гнучкість системи, контролюючи доступ до об'єктів, додаючи додаткову поведінку, як кешування, безпеку або ліниве завантаження, а також знижуючи складність. Використовуючи проксі, можна оптимізувати продуктивність, управляти дозволами та абстрагувати складність від клієнтського коду.
Від віртуальних проксі, які відсувають дорогі обчислення, до защищених проксі, що забезпечують належну авторизацію, до віддалених проксі для доступу до розподілених сервісів — шаблон Proxy є потужним інструментом для багатьох практичних сценаріїв у сучасній розробці програмного забезпечення.
Перекладено з: A Guide to the Proxy Design Pattern in TypeScript and Node.js with Practical Examples 💻