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

Приведи пример использования паттерна прокси

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

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

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

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

Приведи пример использования паттерна прокси

Что такое паттерн Proxy?

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

Основные сценарии использования

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

Практический пример: Прокси для базы данных

// Интерфейс сервиса
public interface UserService {
    User getUserById(Long id);
    void saveUser(User user);
}

// Реальная реализация
public class UserServiceImpl implements UserService {
    private UserRepository repository;
    
    @Override
    public User getUserById(Long id) {
        System.out.println("Запрос к БД: SELECT * FROM users WHERE id = " + id);
        return repository.findById(id).orElse(null);
    }
    
    @Override
    public void saveUser(User user) {
        System.out.println("Сохранение пользователя: " + user.getName());
        repository.save(user);
    }
}

// Прокси с логированием и кэшированием
public class CachedUserServiceProxy implements UserService {
    private final UserService realService;
    private final Map<Long, User> cache = new HashMap<>();
    private static final Logger logger = LoggerFactory.getLogger(CachedUserServiceProxy.class);
    
    public CachedUserServiceProxy(UserService realService) {
        this.realService = realService;
    }
    
    @Override
    public User getUserById(Long id) {
        logger.info("Попытка получить пользователя с ID: {}", id);
        
        // Проверяем кэш
        if (cache.containsKey(id)) {
            logger.info("Найдено в кэше: {}", id);
            return cache.get(id);
        }
        
        // Если в кэше нет, идём в БД
        logger.info("Кэш miss, обращаемся к БД");
        User user = realService.getUserById(id);
        
        if (user != null) {
            cache.put(id, user);
        }
        
        return user;
    }
    
    @Override
    public void saveUser(User user) {
        logger.info("Сохранение пользователя: {}", user.getName());
        realService.saveUser(user);
        // Инвалидируем кэш
        cache.remove(user.getId());
    }
}

// Использование
public class Main {
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();
        UserService cachedService = new CachedUserServiceProxy(realService);
        
        // Первый вызов — обращение к БД
        User user1 = cachedService.getUserById(1L);  // Из БД
        
        // Второй вызов — возврат из кэша
        User user2 = cachedService.getUserById(1L);  // Из кэша
        
        // Сохранение инвалидирует кэш
        User newUser = new User(1L, "John");
        cachedService.saveUser(newUser);
        
        // Снова обращаемся к БД
        User user3 = cachedService.getUserById(1L);  // Из БД
    }
}

Пример с контролем доступа

// Интерфейс
public interface AdminService {
    void deleteUser(Long userId);
    void blockUser(Long userId);
}

// Реальная реализация
public class AdminServiceImpl implements AdminService {
    @Override
    public void deleteUser(Long userId) {
        System.out.println("Пользователь " + userId + " удалён");
    }
    
    @Override
    public void blockUser(Long userId) {
        System.out.println("Пользователь " + userId + " заблокирован");
    }
}

// Прокси с проверкой прав доступа
public class SecureAdminServiceProxy implements AdminService {
    private final AdminService realService;
    private final User currentUser;
    
    public SecureAdminServiceProxy(AdminService realService, User currentUser) {
        this.realService = realService;
        this.currentUser = currentUser;
    }
    
    @Override
    public void deleteUser(Long userId) {
        if (!currentUser.hasRole("SUPER_ADMIN")) {
            throw new SecurityException("Нет прав для удаления пользователей");
        }
        realService.deleteUser(userId);
    }
    
    @Override
    public void blockUser(Long userId) {
        if (!currentUser.hasRole("ADMIN") && !currentUser.hasRole("SUPER_ADMIN")) {
            throw new SecurityException("Нет прав для блокировки пользователей");
        }
        realService.blockUser(userId);
    }
}

Ленивая инициализация с Proxy

public class LazyResourceProxy implements Resource {
    private Resource realResource;
    private boolean initialized = false;
    
    @Override
    public void load() {
        if (!initialized) {
            System.out.println("Инициализация ресурса...");
            realResource = new HeavyResource();
            initialized = true;
        }
    }
    
    @Override
    public String getData() {
        load();  // Ленивая инициализация
        return realResource.getData();
    }
}

Динамический прокси через Java Reflection

// Создание прокси на лету
UserService realService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class},
    new InvocationHandler() {
        @Override
        public Object invoke(Object target, Method method, Object[] args) throws Throwable {
            System.out.println("Вызов метода: " + method.getName());
            long start = System.currentTimeMillis();
            
            Object result = method.invoke(realService, args);
            
            long duration = System.currentTimeMillis() - start;
            System.out.println("Время выполнения: " + duration + "ms");
            
            return result;
        }
    }
);

proxy.getUserById(1L);  // Логирование и измерение времени автоматически

Сравнение с подобными паттернами

ПаттернНазначениеПроблема
ProxyКонтроль доступа, логированиеТребует интерфейса
DecoratorДобавление функционалаМожет создавать цепочку
FacadeУпрощение интерфейсаСкрывает детали
AdapterСовместимость интерфейсовКонвертирует типы

Ключевые преимущества

  • Прозрачность — клиент не знает, что работает с прокси
  • Гибкость — лёгко добавлять новые функции без изменения реального сервиса
  • Разделение ответственности — бизнес-логика отделена от вспомогательной
  • Безопасность — контроль доступа и аудит

Недостатки

  • Производительность — дополнительный слой вызовов
  • Сложность — может быть сложно отследить вызовы
  • Требует интерфейса — нельзя использовать с финальными классами

Паттерн Proxy — это один из самых полезных паттернов в практической разработке, особенно при работе с внешними сервисами, БД и API.