Proxy Pattern

Паттерн Прокси для контроля доступа к объектам и перехвата операций

Паттерн Прокси (Proxy Pattern) предоставляет объект-заместитель, который контролирует доступ к другому объекту. Прокси перехватывает операции (чтение, запись, вызов методов) и может добавлять дополнительную логику: валидацию, логирование, кэширование, защиту свойств и т.д. В JavaScript паттерн реализован через встроенный объект Proxy, который позволяет создавать прокси для любого объекта с помощью ловушек (traps).

В примерах ниже показаны различные применения Proxy: валидация данных при установке свойств, логирование операций, защита приватных свойств и кэширование результатов функций.

// Пример 1: Валидация свойств
const userValidator = {
  set(target, property, value) {
    if (property === 'age' && (typeof value !== 'number' || value < 0 || value > 150)) {
      throw new TypeError('Age must be a number between 0 and 150');
    }
    if (property === 'email' && !value.includes('@')) {
      throw new TypeError('Email must contain @ symbol');
    }
    target[property] = value;
    return true;
  }
};

const user = new Proxy({}, userValidator);
user.name = 'John'; // OK
user.age = 25; // OK
// user.age = -5; // TypeError: Age must be a number between 0 and 150
// user.email = 'invalid'; // TypeError: Email must contain @ symbol

// Пример 2: Логирование операций
const logger = {
  get(target, property) {
    console.log(`Getting property: ${property}`);
    return target[property];
  },
  set(target, property, value) {
    console.log(`Setting property: ${property} = ${value}`);
    target[property] = value;
    return true;
  }
};

const loggedObject = new Proxy({ name: 'Test' }, logger);
loggedObject.name; // Logs: "Getting property: name"
loggedObject.age = 30; // Logs: "Setting property: age = 30"

// Пример 3: Защита приватных свойств
const protectedObject = {
  _private: 'secret',
  public: 'visible'
};

const protectionProxy = new Proxy(protectedObject, {
  get(target, property) {
    if (property.startsWith('_')) {
      throw new Error(`Access denied to private property: ${property}`);
    }
    return target[property];
  },
  set(target, property, value) {
    if (property.startsWith('_')) {
      throw new Error(`Cannot modify private property: ${property}`);
    }
    target[property] = value;
    return true;
  }
});

console.log(protectionProxy.public); // "visible"
// console.log(protectionProxy._private); // Error: Access denied to private property: _private

// Пример 4: Кэширование результатов функций
function createCacheProxy(fn) {
  const cache = new Map();

  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args);

      if (cache.has(key)) {
        console.log('Cache hit!');
        return cache.get(key);
      }

      console.log('Computing...');
      const result = target.apply(thisArg, args);
      cache.set(key, result);
      return result;
    }
  });
}

const expensiveFunction = (n) => {
  // Имитация тяжелых вычислений
  let sum = 0;
  for (let i = 0; i < n; i++) {
    sum += i;
  }
  return sum;
};

const cachedFunction = createCacheProxy(expensiveFunction);
console.log(cachedFunction(1000000)); // Computing... (вычисляет)
console.log(cachedFunction(1000000)); // Cache hit! (использует кэш)

// Пример 5: Дефолтные значения для несуществующих свойств
const defaultsProxy = new Proxy({}, {
  get(target, property) {
    if (property in target) {
      return target[property];
    }
    return `Default value for ${property}`;
  }
});

console.log(defaultsProxy.name); // "Default value for name"
defaultsProxy.name = 'John';
console.log(defaultsProxy.name); // "John"