← Назад к вопросам
В чем разница между шаблонами проектирования Adapter, Facade и Proxy?
2.0 Middle🔥 171 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между шаблонами Adapter, Facade и Proxy
Эти три паттерна часто путают, так как они похожи на первый взгляд - все используют обёртку. Однако их цели и использование принципиально различаются.
Adapter (Адаптер)
Цель: Сделать НЕСОВМЕСТИМЫЕ интерфейсы совместимыми. Конвертация интерфейса одного класса в интерфейс другого, который ожидает клиент.
// Старый класс - несовместимый интерфейс
public class OldPaymentGateway {
public void sendMoneyXML(String xmlData) {
// отправляет платёж в формате XML
}
}
// Новый интерфейс, который ожидает система
public interface ModernPaymentGateway {
void processPayment(PaymentRequest request);
}
// Adapter - преобразует старый интерфейс в новый
public class PaymentGatewayAdapter implements ModernPaymentGateway {
private OldPaymentGateway oldGateway;
public PaymentGatewayAdapter(OldPaymentGateway oldGateway) {
this.oldGateway = oldGateway;
}
@Override
public void processPayment(PaymentRequest request) {
// Адаптируем новый интерфейс к старому
String xmlData = convertToXML(request);
oldGateway.sendMoneyXML(xmlData);
}
private String convertToXML(PaymentRequest request) {
// Логика конвертации
return "<?xml version=\"1.0\"?>...";
}
}
// Использование
OldPaymentGateway oldGateway = new OldPaymentGateway();
ModernPaymentGateway adapter = new PaymentGatewayAdapter(oldGateway);
PaymentRequest request = new PaymentRequest(100, "USD");
adapter.processPayment(request); // Работает, несмотря на несовместимость
Задача Adapter:
- Интеграция с legacy кодом
- Использование библиотек с несовместимыми интерфейсами
- Миграция старого кода на новый
- Adapter работает как "переводчик"
Facade (Фасад)
Цель: Предоставить УПРОЩЁННЫЙ интерфейс к СЛОЖНОЙ подсистеме. Скрыть сложность и предоставить единую точку доступа.
// Сложная подсистема с множеством классов
public class PaymentProcessor {
public void processPayment(Payment payment) { /* ... */ }
}
public class FraudDetector {
public boolean isFraud(Payment payment) { /* ... */ }
}
public class NotificationService {
public void notifyUser(User user, String message) { /* ... */ }
}
public class AccountingService {
public void recordTransaction(Payment payment) { /* ... */ }
}
public class ReportingService {
public void generateReport(Payment payment) { /* ... */ }
}
// Facade - упрощает работу с подсистемой
public class PaymentFacade {
private PaymentProcessor processor;
private FraudDetector fraudDetector;
private NotificationService notifier;
private AccountingService accounting;
private ReportingService reporting;
public PaymentFacade() {
this.processor = new PaymentProcessor();
this.fraudDetector = new FraudDetector();
this.notifier = new NotificationService();
this.accounting = new AccountingService();
this.reporting = new ReportingService();
}
/**
* Простой метод, скрывающий сложность
*/
public void executePayment(User user, Payment payment) {
// Проверка мошенничества
if (fraudDetector.isFraud(payment)) {
throw new PaymentException("Suspicious transaction");
}
// Обработка платежа
processor.processPayment(payment);
// Уведомление пользователя
notifier.notifyUser(user, "Payment successful");
// Запись в учёт
accounting.recordTransaction(payment);
// Отчётность
reporting.generateReport(payment);
}
}
// Использование - очень просто!
PaymentFacade facade = new PaymentFacade();
facade.executePayment(user, payment);
Задача Facade:
- Скрыть сложность подсистемы
- Предоставить простой интерфейс
- Слабая связанность между компонентами
- Facade инкапсулирует целую подсистему
Proxy (Прокси)
Цель: Контролировать ДОСТУП к реальному объекту. Замещает другой объект и перехватывает вызовы к нему.
// Реальный объект, доступ к которому нужно контролировать
public interface DocumentService {
Document getDocument(String id);
void updateDocument(Document doc);
}
public class RealDocumentService implements DocumentService {
private Database database;
@Override
public Document getDocument(String id) {
// Реальная работа - долгая операция
return database.fetch(id);
}
@Override
public void updateDocument(Document doc) {
database.save(doc);
}
}
// Proxy - контролирует доступ
public class DocumentServiceProxy implements DocumentService {
private RealDocumentService realService;
private User currentUser;
private Map<String, Document> cache = new ConcurrentHashMap<>();
public DocumentServiceProxy(RealDocumentService realService, User currentUser) {
this.realService = realService;
this.currentUser = currentUser;
}
@Override
public Document getDocument(String id) {
// Контроль доступа - проверка прав
if (!hasAccessPermission(id)) {
throw new AccessDeniedException("No access to document " + id);
}
// Кэширование - оптимизация
if (cache.containsKey(id)) {
return cache.get(id);
}
// Логирование - отслеживание доступа
logAccess(id);
// Делегирование реальному объекту
Document doc = realService.getDocument(id);
// Кэширование результата
cache.put(id, doc);
return doc;
}
@Override
public void updateDocument(Document doc) {
// Контроль доступа - только владелец может обновлять
if (!isOwner(doc)) {
throw new AccessDeniedException("Only owner can update");
}
// Логирование
logModification(doc);
// Делегирование реальному объекту
realService.updateDocument(doc);
// Инвалидация кэша
cache.remove(doc.getId());
}
private boolean hasAccessPermission(String docId) {
// Проверка прав доступа
return true;
}
private boolean isOwner(Document doc) {
return doc.getOwnerId().equals(currentUser.getId());
}
private void logAccess(String docId) {
System.out.println("User " + currentUser.getId() + " accessed " + docId);
}
private void logModification(Document doc) {
System.out.println("User " + currentUser.getId() + " modified " + doc.getId());
}
}
// Использование
RealDocumentService realService = new RealDocumentService();
DocumentService proxy = new DocumentServiceProxy(realService, currentUser);
// Proxy контролирует все вызовы
Document doc = proxy.getDocument("doc123"); // Проверка прав + кэширование + логирование
proxy.updateDocument(doc); // Проверка прав + логирование
Сравнительная таблица
| Аспект | Adapter | Facade | Proxy |
|---|---|---|---|
| Цель | Совместимость интерфейсов | Упрощение сложности | Контроль доступа |
| Сложность | Две несовместимые системы | Сложная подсистема | Один объект |
| Интерфейс | Может изменяться | Упрощённый | Тот же |
| Связь с оригиналом | 1:1 адаптация | Скрывает множество | 1:1 делегирование |
| Создание | Обёртка для совместимости | Агрегирует подсистему | Замещает объект |
| Примеры | Legacy интеграция | REST API контроллер | Security, Caching, Logging |
Реальные примеры из Spring Framework
Adapter:
// Spring использует Adapter для интеграции разных библиотек
// Например: HttpClient -> RestTemplate
public class RestTemplate implements HttpAccessor {
private HttpClient httpClient; // Старая библиотека
// Адаптирует старый интерфейс к новому
public ResponseEntity<String> getForEntity(String url) {
// Конвертирует HttpClient в ResponseEntity
}
}
Facade:
// Spring контроллер - это Facade для бизнес-логики
@RestController
public class OrderController {
@PostMapping("/orders")
public ResponseEntity<?> createOrder(@RequestBody OrderRequest req) {
// Скрывает сложность: валидация, сохранение, уведомления и т.д.
orderFacade.createOrder(req);
return ResponseEntity.ok("Created");
}
}
Proxy:
// Spring использует Proxy для реализации функций
@Transactional // Proxy с функциональностью транзакций
public class UserService {
public void saveUser(User user) {
// Proxy добавляет управление транзакциями
// Proxy также может добавлять кэширование (@Cacheable)
}
}
Ключевые отличия
// ADAPTER - делает несовместимое совместимым
OldInterface old = new OldImplementation();
NewInterface compatible = new AdapterPattern(old); // Адаптация
// FACADE - упрощает сложность
ComplexSubsystem sub = new ComplexSubsystem();
SimpleInterface simple = new FacadePattern(sub); // Упрощение
// PROXY - контролирует доступ
RealObject real = new RealObject();
SameInterface controlled = new ProxyPattern(real); // Контроль
Вывод
- Adapter нужен когда у вас есть ДВА несовместимых интерфейса, которые нужно объединить
- Facade нужен когда у вас сложная подсистема, которую нужно упростить для клиентов
- Proxy нужен когда вы хотите контролировать доступ/поведение реального объекта
Выбор паттерна зависит от ПРОБЛЕМЫ, которую вы решаете, а не от деталей реализации.