← Назад к вопросам
Для чего нужен паттерн проектирования Proxy?
2.0 Middle🔥 141 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Паттерн Proxy (Прокси)
Определение
Proxy — это структурный паттерн проектирования, который предоставляет объект-заместитель (прокси) для управления доступом к другому объекту. Прокси контролирует все взаимодействия с оригинальным объектом и может добавлять дополнительную логику.
Назначение
Proxy используется для:
- Ленивой инициализации (Lazy Loading) — создать дорогой объект только при необходимости
- Контроля доступа — проверить права перед доступом
- Логирования и мониторинга — отследить все операции
- Кэширования — сохранить результаты дорогих операций
- Безопасности — валидировать запросы
Классическая структура
// 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
- Контроль доступа — легко добавить проверку прав
- Ленивая инициализация — создаем объекты только при необходимости
- Кэширование — сохраняем дорогие вычисления
- Логирование — отслеживаем все операции
- Оригинальный объект не меняется — соблюдаем Open/Closed принцип
Минусы Proxy
- Усложнение кода — добавляется еще один слой
- Небольшое замедление — каждый вызов проходит через прокси
- Может быть сложно отладить — если много прокси в цепочке
Когда использовать
- Контроль доступа к объектам
- Ленивая загрузка тяжелых ресурсов
- Кэширование результатов
- Логирование операций
- Добавление функционала без изменения оригинального класса
Proxy — мощный паттерн для разделения ответственности и управления доступом.