Что такое паттерн Proxy и каковы его основные типы?

Ответ

Паттерн Proxy (Заместитель) — это структурный паттерн проектирования, который предоставляет объект-заменитель или placeholder для другого объекта. Прокси контролирует доступ к оригинальному объекту, позволяя выполнить дополнительную логику до или после основного вызова.

Классическая структура:

  1. Subject (Интерфейс): Общий интерфейс для RealSubject и Proxy.
  2. RealSubject (Реальный объект): Основной объект, содержащий бизнес-логику.
  3. Proxy (Заместитель): Объект, хранящий ссылку на RealSubject, управляет доступом к нему.

Пример: Virtual Proxy для ленивой загрузки изображения

// Subject
interface Image {
    void display();
}

// RealSubject
class RealImage implements Image {
    private String fileName;
    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(); // Ресурсоемкая операция
    }
    private void loadFromDisk() { System.out.println("Loading: " + fileName); }
    public void display() { System.out.println("Displaying: " + fileName); }
}

// Proxy
class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;
    public ProxyImage(String fileName) { this.fileName = fileName; }
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(fileName); // Создание отложено до первого вызова
        }
        realImage.display();
    }
}

// Клиентский код
Image image = new ProxyImage("photo.jpg");
image.display(); // Вывод: "Loading: photo.jpg" -> "Displaying: photo.jpg"
image.display(); // Вывод: "Displaying: photo.jpg" (объект уже загружен)

Основные типы прокси:

  • Виртуальный прокси (Virtual Proxy): Откладывает создание ресурсоемкого объекта до момента его реального использования (ленивая загрузка).
  • Защищающий прокси (Protection Proxy): Контролирует доступ к объекту на основе прав вызывающего кода.
  • Удаленный прокси (Remote Proxy): Представляет локальную заглушку для объекта, находящегося в другом адресном пространстве (например, RMI, gRPC).
  • Кэширующий прокси (Caching Proxy): Сохраняет результаты запросов и возвращает кэшированные данные для повторных вызовов.
  • Логирующий прокси (Logging Proxy): Регистрирует вызовы методов и их параметры.

В Java прокси часто создаются динамически с помощью java.lang.reflect.Proxy или библиотек (например, Spring AOP).

Ответ 18+ 🔞

Да ты посмотри, какой хитрый паттерн, ёпта! Прям как мой сосед, который за меня квартплату получает, а потом мне отдаёт, но только после того, как я ему пива куплю. Это и есть Proxy, сука — Заместитель!

Смысл в чём, блядь? Есть у тебя какой-то важный, нагруженный объект — RealSubject. И чтобы к нему не лезли все подряд, когда не надо, или чтобы он не грузился раньше времени, ты ставишь перед ним охранника — Proxy. Этот охранник имеет ту же самую рожу (интерфейс Subject), но внутри он решает: пускать тебя к боссу сейчас или нет, а может, вообще сам ответит.

Вот смотри, классическая разводка, три персонажа:

  1. Subject (Интерфейс) — Это как удостоверение личности, блядь. И настоящий объект, и его заместитель должны его предъявить, чтобы ты понимал — они из одной конторы.
  2. RealSubject (Настоящий объект) — Это тот самый тяжёлый мужик, который делает всю основную работу. Создавать его — долго и дорого.
  3. Proxy (Заместитель) — А это хитрая жопа, которая прикидывается тем самым мужиком. Пока ты не позвал её по-настоящему, она RealSubject даже создавать не будет. А создаст — так ещё и проконтролирует, как ты к нему обращаешься.

Пример: Виртуальный прокси для картинки (ленивая загрузка)

Представь, у тебя сайт, а на нём фотка весом в овердохуища мегабайт. Загружать её сразу при открытии страницы — идиотизм, пользователь сбежит. Вот тут-то и выходит наш подставной актёр!

// Subject — Удостоверение. Все, кто хочет быть картинкой, должны уметь display().
interface Image {
    void display();
}

// RealSubject — Настоящая, тяжёлая картинка. Её создание — это боль.
class RealImage implements Image {
    private String fileName;
    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(); // Опа, а вот и боль! Сразу с диска грузим, долго и нудно.
    }
    private void loadFromDisk() { System.out.println("Loading: " + fileName); }
    public void display() { System.out.println("Displaying: " + fileName); }
}

// Proxy — Подставная картинка-обманка. Пока все думают, что это она.
class ProxyImage implements Image {
    private RealImage realImage; // Ссылка на босса, но изначально её нет, блядь!
    private String fileName;
    public ProxyImage(String fileName) { this.fileName = fileName; }
    public void display() {
        // А вот и магия! Пока не позвали display, RealImage не создавался!
        if (realImage == null) {
            realImage = new RealImage(fileName); // Создаём только сейчас! Лениво, сука!
        }
        realImage.display(); // А работу пусть настоящий мужик делает.
    }
}

// Клиентский код
Image image = new ProxyImage("photo.jpg"); // Создали прокси быстро, как фастфуд.
image.display(); // Первый вызов: "Loading: photo.jpg" -> "Displaying: photo.jpg"
image.display(); // Второй вызов: "Displaying: photo.jpg" (объект уже загружен, не грузим повторно)

Видал? В первый раз он создал настоящий объект, а во второй — уже работает с готовым. Экономия, блядь!

Какие ещё бывают заместители, кроме ленивых?

  • Защищающий прокси (Protection Proxy): Как вышибала в клубе. Проверяет, есть ли у тебя права (роль, токен) лезть к объекту. Нет прав — пошёл нахуй, в исключение.
  • Удалённый прокси (Remote Proxy): Это вообще шаманство. Объект-то на другом сервере, за океаном, а у тебя тут его местная кукла (stub). Ты её дергаешь, а она по сети запросы шлёт. RPC, gRPC — всё отсюда растёт.
  • Кэширующий прокси (Caching Proxy): Умная жопа. Запомнил результат твоего первого запроса в базу, и когда ты второй раз приполз с тем же вопросом — херак тебе ответ из памяти, без нагрузки на базу. Быстро и сердито.
  • Логирующий прокси (Logging Proxy): Стукач, блядь. Пропускает все вызовы к объекту, но перед этим пишет в лог: "Такой-то метод вызвали с такими-то параметрами в пять часов утра".

В Java это всё можно делать не вручную, а через ебушки-воробушки рефлексию — java.lang.reflect.Proxy. Или через Spring AOP, который на лету, сука, оборачивает твои бины в прокси, чтобы логировать, транзакции открывать или кэшировать. Красота, а не жизнь!