Объясните различия между паттернами адаптер, декоратор и прокси, их назначения и области применения.
Ответ от нейросети
sobes.tech AI
Что хотят услышать интервьюеры:
Адаптер нужен, чтобы сделать несовместимый интерфейс удобным для использования. Декоратор добавляет объекту поведение, не меняя его класс. Прокси управляет доступом к объекту и может добавлять логику вокруг вызова, например кэширование или ленивую загрузку. Важно уметь отличить: адаптер про совместимость, декоратор про расширение, прокси про контроль доступа.
Определение:
Adapter преобразует интерфейс одного класса в интерфейс, который ожидает клиент. Он решает проблему несовместимости между уже существующими компонентами.
Decorator динамически добавляет объекту новое поведение, оборачивая его в объект с тем же интерфейсом. Это способ гибко расширять функциональность без наследования.
Proxy предоставляет заместителя для другого объекта и контролирует обращение к нему. Прокси может откладывать создание объекта, ограничивать доступ, логировать вызовы или кешировать результаты.
Пример использования:
- Adapter: есть библиотека с методом
fetchData(), а приложение ожидаетgetData(). Адаптер переводит вызов между ними. - Decorator: поток ввода оборачивается в буферизацию и сжатие, где каждый слой добавляет свою функцию.
- Proxy: удалённый сервис или тяжёлый объект создаётся не сразу, а через прокси, который инициализирует его только при первом обращении.
// Adapter
interface Target {
void request();
}
class Adaptee {
void specificRequest() {
System.out.println("specificRequest");
}
}
class Adapter implements Target {
private final Adaptee adaptee = new Adaptee();
@Override
public void request() {
adaptee.specificRequest();
}
}
// Decorator
interface Notifier {
void send(String message);
}
class EmailNotifier implements Notifier {
public void send(String message) {
System.out.println("Email: " + message);
}
}
class SmsDecorator implements Notifier {
private final Notifier notifier;
SmsDecorator(Notifier notifier) {
this.notifier = notifier;
}
public void send(String message) {
notifier.send(message);
System.out.println("SMS: " + message);
}
}
// Proxy
class RealService {
void execute() {
System.out.println("Real service execution");
}
}
class ServiceProxy {
private RealService realService;
void execute() {
if (realService == null) {
realService = new RealService();
}
System.out.println("Access control / logging");
realService.execute();
}
}
Пояснение кода:
В примере с Adapter класс Adapter реализует интерфейс Target, который ожидает клиент, и внутри вызывает Adaptee.specificRequest(). Снаружи клиент работает с привычным методом request(), не зная о несовместимом классе.
В примере с Decorator SmsDecorator реализует тот же интерфейс Notifier, что и базовая реализация EmailNotifier, и содержит внутри другой Notifier. При вызове send() декоратор сначала делегирует вызов обёрнутому объекту, а затем добавляет свою логику — отправку SMS.
В примере с Proxy класс ServiceProxy управляет созданием RealService и может выполнять дополнительную логику до реального вызова. Если объект ещё не создан, прокси создаёт его лениво, а затем делегирует выполнение.
Ключевые моменты:
- Adapter меняет интерфейс, чтобы несовместимые классы могли работать вместе.
- Decorator сохраняет интерфейс, но добавляет новое поведение слоями.
- Proxy сохраняет интерфейс и контролирует доступ к реальному объекту.
- У Decorator и Proxy внешний интерфейс часто одинаковый, но цель разная: у декоратора расширение, у прокси — контроль.
- У Adapter обычно есть один адаптируемый объект, который переводится в нужный формат.
- На собеседовании полезно приводить примеры: адаптер — интеграция старого API, декоратор —
InputStream/OutputStream, прокси — lazy loading, security, caching.