Ответ
Паттерны «Адаптер» (Adapter), «Фасад» (Facade) и «Прокси» (Proxy) относятся к структурным паттернам, но решают разные проблемы, связанные с интерфейсами и доступом.
Адаптер (Adapter)
- Проблема: Несовместимость интерфейсов. Есть клиент, который ожидает интерфейс
A, и есть полезный класс с интерфейсомB. - Решение: Создать класс-обертку (Адаптер), который преобразует интерфейс класса
Bв интерфейсA, ожидаемый клиентом. - Аналогия: Переходник для розетки.
Пример (Адаптер для старого класса):
// Целевой интерфейс, который ожидает клиент
interface ModernPrinter {
void printDocument(String text);
}
// Старый, несовместимый класс
class LegacyPrinter {
void print(String text, int copies) { /*...*/ }
}
// Адаптер
class LegacyPrinterAdapter implements ModernPrinter {
private LegacyPrinter legacyPrinter;
public LegacyPrinterAdapter(LegacyPrinter printer) {
this.legacyPrinter = printer;
}
@Override
public void printDocument(String text) {
// Адаптируем вызов: преобразуем новый интерфейс в старый
legacyPrinter.print(text, 1);
}
}
// Клиентский код работает с ModernPrinter, не зная о LegacyPrinter
ModernPrinter printer = new LegacyPrinterAdapter(new LegacyPrinter());
printer.printDocument("Hello");
Фасад (Facade)
- Проблема: Слишком сложная подсистема с множеством классов и зависимостей.
- Решение: Создать единый упрощенный интерфейс (Фасад), который скрывает сложность внутренней подсистемы и предоставляет клиенту только необходимый набор функций.
- Аналогия: Единый пульт управления для домашнего кинотеатра.
Пример (Фасад для запуска компьютера):
// Сложная подсистема
class CPU { void boot() { /*...*/ } }
class Memory { void loadOS() { /*...*/ } }
class HardDrive { void readBootSector() { /*...*/ } }
// Упрощенный Фасад
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hdd;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hdd = new HardDrive();
}
public void start() {
// Инкапсулирует сложную последовательность запуска
cpu.boot();
hdd.readBootSector();
memory.loadOS();
System.out.println("Computer ready!");
}
}
// Клиенту нужно знать только про Фасад
ComputerFacade computer = new ComputerFacade();
computer.start(); // Вместо вызова 3-х отдельных классов в правильном порядке
Прокси (Proxy)
- Проблема: Необходимость контролировать доступ к объекту или добавлять дополнительную логику при обращении к нему.
- Решение: Создать объект-заместитель (Прокси), который имеет тот же интерфейс, что и реальный объект, и перехватывает вызовы к нему для добавления своей функциональности.
- Аналогия: Секретарь, который фильтрует звонки к руководителю.
Пример (Прокси для ленивой загрузки изображения):
interface Image {
void display();
}
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // Дорогая операция
}
private void loadFromDisk() { System.out.println("Loading " + filename); }
@Override
public void display() { System.out.println("Displaying " + filename); }
}
class ProxyImage implements Image { // Имеет тот же интерфейс
private RealImage realImage;
private String filename;
public ProxyImage(String filename) { this.filename = filename; }
@Override
public void display() {
// Ленивая инициализация: создаем реальный объект только при необходимости
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display(); // Делегируем вызов реальному объекту
}
}
// Клиент может использовать Proxy так же, как RealImage
Image image = new ProxyImage("photo.jpg");
// RealImage еще не создан
image.display(); // Создается RealImage и отображается
image.display(); // RealImage уже создан, просто отображается
Сравнительная таблица
| Критерий | Адаптер (Adapter) | Фасад (Facade) | Прокси (Proxy) |
|---|---|---|---|
| Основная цель | Преобразовать один интерфейс в другой | Упростить сложный интерфейс подсистемы | Контролировать доступ к объекту (заместитель) |
| Структурные изменения | Меняет интерфейс | Предоставляет новый, упрощенный интерфейс | Сохраняет исходный интерфейс |
| Количество объектов | Работает обычно с одним несовместимым объектом | Работает с целой подсистемой объектов | Работает с одним объектом, который он представляет |
| Время применения | Post-factum, для интеграции существующего кода | Предварительно, для проектирования удобного слоя доступа | Post-factum или изначально, для добавления логики доступа |
| Типичные сценарии | Интеграция сторонних библиотек, работа с legacy-кодом | Упрощение работы со сложными фреймворками (например, низкоуровневое API) | Ленивая загрузка, кэширование, защита доступа, логирование, удаленные прокси (RPC) |