← Назад к вопросам

Для чего нужен паттерн проектирования Proxy?

2.0 Middle🔥 141 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Паттерн Proxy (Прокси)

Определение

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

Назначение

Proxy используется для:

  1. Ленивой инициализации (Lazy Loading) — создать дорогой объект только при необходимости
  2. Контроля доступа — проверить права перед доступом
  3. Логирования и мониторинга — отследить все операции
  4. Кэширования — сохранить результаты дорогих операций
  5. Безопасности — валидировать запросы

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

// 1. Интерфейс
public interface DataService {
    String getData(String key);
}

// 2. Реальный объект (дорогой в создании/использовании)
public class RealDataService implements DataService {
    public RealDataService() {
        System.out.println("Создаю дорогой объект (5 сек)");
        // Инициализация БД, загрузка конфига и т.д.
    }
    
    @Override
    public String getData(String key) {
        System.out.println("Получаю данные из БД: " + key);
        return "Данные для " + key;
    }
}

// 3. Proxy (посредник)
public class DataServiceProxy implements DataService {
    private RealDataService realService;
    private static final Set<String> ADMIN_KEYS = Set.of("admin", "root");
    
    @Override
    public String getData(String key) {
        // Проверка доступа
        if (!ADMIN_KEYS.contains(key)) {
            throw new SecurityException("Доступ запрещен для ключа: " + key);
        }
        
        // Ленивая инициализация
        if (realService == null) {
            realService = new RealDataService();
        }
        
        // Делегируем работу
        return realService.getData(key);
    }
}

// 4. Использование
DataService service = new DataServiceProxy();
String data = service.getData("admin");  // OK
String data = service.getData("user");   // SecurityException

Практический пример 1: Ленивая загрузка изображения

public interface Image {
    void display();
}

public class RealImage implements Image {
    private String filename;
    
    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();  // Дорогая операция
    }
    
    private void loadFromDisk() {
        System.out.println("Загружаю изображение: " + filename);
        // Реальная загрузка 100MB файла
    }
    
    @Override
    public void display() {
        System.out.println("Показываю: " + filename);
    }
}

public class ProxyImage implements Image {
    private String filename;
    private RealImage realImage;
    
    public ProxyImage(String filename) {
        this.filename = filename;  // Не загружаем, только запоминаем
    }
    
    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);  // Загружаем только при display()
        }
        realImage.display();
    }
}

// Использование
List<Image> images = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    images.add(new ProxyImage("image_" + i + ".jpg"));  // Быстро, загрузок нет
}

// Только показанные изображения будут загружены
images.get(0).display();  // Теперь загружается
images.get(999).display();  // Загружается

Практический пример 2: Кэширование результатов

public interface Calculator {
    int expensive(int x);
}

public class RealCalculator implements Calculator {
    @Override
    public int expensive(int x) {
        System.out.println("Выполняю тяжелые вычисления...");
        try {
            Thread.sleep(3000);  // Имитация 3-секундного вычисления
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return x * x * x;
    }
}

public class CachingCalculatorProxy implements Calculator {
    private final RealCalculator realCalculator = new RealCalculator();
    private final Map<Integer, Integer> cache = new ConcurrentHashMap<>();
    
    @Override
    public int expensive(int x) {
        return cache.computeIfAbsent(x, k -> realCalculator.expensive(k));
    }
}

// Использование
Calculator calc = new CachingCalculatorProxy();
System.out.println(calc.expensive(5));  // 3 сек, вычисляем
System.out.println(calc.expensive(5));  // 0 мс, из кэша
System.out.println(calc.expensive(5));  // 0 мс, из кэша

Практический пример 3: Логирование и мониторинг

public interface BankAccount {
    void withdraw(double amount);
    void deposit(double amount);
    double getBalance();
}

public class BankAccountProxy implements BankAccount {
    private final BankAccount realAccount;
    private final Logger logger = LoggerFactory.getLogger(BankAccountProxy.class);
    
    public BankAccountProxy(BankAccount realAccount) {
        this.realAccount = realAccount;
    }
    
    @Override
    public void withdraw(double amount) {
        logger.info("Попытка снять {}₽", amount);
        try {
            realAccount.withdraw(amount);
            logger.info("Успешно снято {}₽", amount);
        } catch (Exception e) {
            logger.error("Ошибка при снятии {}₽: {}", amount, e.getMessage());
            throw e;
        }
    }
    
    @Override
    public void deposit(double amount) {
        logger.info("Попытка положить {}₽", amount);
        realAccount.deposit(amount);
        logger.info("Успешно положено {}₽", amount);
    }
    
    @Override
    public double getBalance() {
        return realAccount.getBalance();
    }
}

Proxy в Java стандартной библиотеке

1. java.lang.reflect.Proxy

// Dynamic Proxy — создание прокси во время выполнения
DataService service = (DataService) Proxy.newProxyInstance(
    DataService.class.getClassLoader(),
    new Class[] { DataService.class },
    (proxy, method, args) -> {
        System.out.println("Вызвали: " + method.getName());
        return method.invoke(realService, args);
    }
);

2. Spring @Transactional

@Service
public class UserService {
    @Transactional  // Под капотом создается прокси
    public void createUser(User user) {
        // Spring создает прокси, который управляет транзакцией
    }
}

3. Collections.unmodifiableList()

List<String> list = new ArrayList<>();
list.add("item");

List<String> unmodifiable = Collections.unmodifiableList(list);
// Это тоже прокси — попытка модифицировать выбросит исключение
unmodifiable.add("new");  // UnsupportedOperationException

Плюсы Proxy

  1. Контроль доступа — легко добавить проверку прав
  2. Ленивая инициализация — создаем объекты только при необходимости
  3. Кэширование — сохраняем дорогие вычисления
  4. Логирование — отслеживаем все операции
  5. Оригинальный объект не меняется — соблюдаем Open/Closed принцип

Минусы Proxy

  1. Усложнение кода — добавляется еще один слой
  2. Небольшое замедление — каждый вызов проходит через прокси
  3. Может быть сложно отладить — если много прокси в цепочке

Когда использовать

  • Контроль доступа к объектам
  • Ленивая загрузка тяжелых ресурсов
  • Кэширование результатов
  • Логирование операций
  • Добавление функционала без изменения оригинального класса

Proxy — мощный паттерн для разделения ответственности и управления доступом.

Для чего нужен паттерн проектирования Proxy? | PrepBro