Вопрос проверяет понимание паттернов проектирования Proxy и Adapter, их целей и отличий, что важно для написания гибкого и поддерживаемого кода.
Паттерны Proxy и Adapter относятся к категории структурных паттернов проектирования, которые помогают организовать отношения между объектами. Хотя оба работают как обёртки (wrappers) вокруг других объектов, их цели и способы применения принципиально различны.
Adapter (Адаптер) используется, когда у вас есть класс (или объект) с нужной функциональностью, но его интерфейс не соответствует тому, который ожидает клиентский код. Адаптер преобразует интерфейс одного класса в интерфейс, понятный другому классу. Это позволяет работать вместе классам, которые изначально не были предназначены для этого.
Пример из жизни: переходник для розетки. У вас есть вилка от американского устройства (интерфейс A), но розетка европейская (интерфейс B). Адаптер (переходник) позволяет соединить их, не меняя ни устройство, ни розетку.
Proxy (Заместитель) предоставляет объект-заместитель, который контролирует доступ к другому объекту. Интерфейс Proxy идентичен интерфейсу оригинального объекта, поэтому клиент может работать с Proxy, не подозревая о подмене. Основные причины использования Proxy:
Предположим, у нас есть старый класс OldPrinter с методом printDocument(), а новый клиентский код ожидает интерфейс NewPrinter с методом print(). Адаптер решит эту проблему.
// Старый класс, который нужно адаптировать
class OldPrinter {
printDocument() {
console.log('Printing document using old printer...');
}
}
// Новый интерфейс, который ожидает клиент
class NewPrinter {
print() {
console.log('Printing...');
}
}
// Адаптер наследует NewPrinter и использует OldPrinter внутри
class PrinterAdapter extends NewPrinter {
constructor(oldPrinter) {
super();
this.oldPrinter = oldPrinter;
}
print() {
// Преобразуем вызов нового интерфейса в старый
this.oldPrinter.printDocument();
}
}
// Использование
const oldPrinter = new OldPrinter();
const adapter = new PrinterAdapter(oldPrinter);
clientCode(adapter); // Клиент работает с NewPrinter, но вызывает старую функциональность
Создадим Proxy для объекта изображения, чтобы реализовать ленивую загрузку (изображение загружается только при первом отображении).
// Интерфейс, который реализуют и реальный объект, и прокси
class Image {
display() {}
}
// Реальный объект, создание которого может быть дорогим
class RealImage extends Image {
constructor(filename) {
super();
this.filename = filename;
this.loadFromDisk();
}
loadFromDisk() {
console.log(`Loading image: ${this.filename}`);
}
display() {
console.log(`Displaying image: ${this.filename}`);
}
}
// Proxy контролирует доступ к RealImage
class ImageProxy extends Image {
constructor(filename) {
super();
this.filename = filename;
this.realImage = null; // Реальный объект создаётся лениво
}
display() {
if (this.realImage === null) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}
// Использование
const image = new ImageProxy('photo.jpg');
// RealImage ещё не создан, загрузки с диска не происходит
image.display(); // Только здесь создаётся и загружается RealImage, затем отображается
Вывод: Используйте Adapter, когда вам нужно привести несовместимые интерфейсы к общему знаменателю, чтобы интегрировать старый код или сторонние библиотеки. Используйте Proxy, когда требуется добавить дополнительный уровень контроля над доступом к объекту (ленивая инициализация, кэширование, безопасность) без изменения его исходного кода или интерфейса, видимого клиенту.