Sobes.tech
Назад к вопросам
Junior — Middle
52

Объясните различия между паттернами адаптер, декоратор и прокси, их назначения и области применения.

Компании, где спрашивали
IT OneIT One

Ответ от нейросети

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.