Заместитель (Proxy)

Proxy предоставляет контракт реального объекта, но контролирует доступ к нему: заместитель решает, когда и как обратиться к цели.

Задача и варианты

  • ленивый proxy откладывает создание дорогого объекта;
  • remote proxy скрывает транспорт и сериализацию;
  • protection proxy проверяет право на операцию;
  • caching proxy повторно использует недавно полученный результат;
  • logging proxy наблюдает вызовы, не меняя клиентский контракт.

Паттерн уместен, когда контроль относится именно к границе доступа. Если цель — сформировать новые комбинации функций, см. Декоратор Decorator.

Роли

  • Subject — общий интерфейс;
  • Real Subject — выполняет основную работу;
  • Proxy — хранит или создаёт Real Subject и перехватывает вызовы;
  • Client — зависит только от Subject.

Пример на Java

interface FeatureFlags { boolean enabled(String key); }
final class RemoteFeatureFlags implements FeatureFlags {
    public boolean enabled(String key) {
        System.out.println("remote lookup: " + key);
        return key.startsWith("stable.");
    }
}
final class CachingFeatureFlags implements FeatureFlags {
    private final FeatureFlags target;
    private final Map<String, Boolean> cache = new HashMap<>();
    CachingFeatureFlags(FeatureFlags target) { this.target = target; }
    @Override public boolean enabled(String key) {
        return cache.computeIfAbsent(key, target::enabled);
    }
}
FeatureFlags flags =
        new CachingFeatureFlags(new RemoteFeatureFlags());
flags.enabled("stable.search");
flags.enabled("stable.search"); // второй вызов берёт значение из cache

Реальному кэширующему proxy нужны TTL, предел размера и стратегия ошибок.

Важные границы

Proxy соблюдает контракт Subject: нельзя кэшировать операцию, каждый вызов которой должен видеть новое значение. Remote proxy не скрывает задержку и частичный отказ: контракт должен позволять обработать timeout и недоступность.

Динамический proxy в Java (java.lang.reflect.Proxy) создаёт реализации интерфейсов во время выполнения. Он удобен для сквозной логики, но ошибки сигнатур обнаруживаются позже, а трассировка становится сложнее.

Компромиссы

  • контроль доступа сосредоточен в одном объекте;
  • дорогой ресурс можно создавать только по необходимости;
  • добавляется косвенность и задержка;
  • состояние кэша требует синхронизации;
  • исключения реального объекта нельзя бездумно скрывать;
  • несколько proxy вокруг одного объекта усложняют порядок вызовов.

Чем отличается

Вопросы на собеседовании

  1. Почему интерфейс общий? Клиент не меняется при включении контроля.
  2. Чем remote proxy опасно считать прозрачным? Сеть добавляет задержку, сериализацию и частичные отказы.
  3. Что нужно кэширующему proxy кроме Map? TTL, предел размера, инвалидация, защита от конкурентной загрузки и политика ошибок.
  4. Когда использовать JDK dynamic proxy? Когда есть интерфейс и одинаковая перехватывающая логика для многих реализаций.
  5. Может ли proxy создавать target лениво? Да, это виртуальный proxy.

Связи: Структурные паттерны, Шаблоны проектирования, Принципы SOLID.