Заместитель (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 вокруг одного объекта усложняют порядок вызовов.
Чем отличается
- Декоратор Decorator расширяет возможности компонента;
- Адаптер Adapter меняет интерфейс;
- Фасад Facade даёт упрощённую точку входа в подсистему;
- Фабричный метод Factory Method управляет созданием, но не каждым вызовом.
Вопросы на собеседовании
- Почему интерфейс общий? Клиент не меняется при включении контроля.
- Чем remote proxy опасно считать прозрачным? Сеть добавляет задержку, сериализацию и частичные отказы.
- Что нужно кэширующему proxy кроме
Map? TTL, предел размера, инвалидация, защита от конкурентной загрузки и политика ошибок. - Когда использовать JDK dynamic proxy? Когда есть интерфейс и одинаковая перехватывающая логика для многих реализаций.
- Может ли proxy создавать target лениво? Да, это виртуальный proxy.
Связи: Структурные паттерны, Шаблоны проектирования, Принципы SOLID.