Facade
Facade — это структурный паттерн проектирования, который предоставляет упрощенный интерфейс к сложной подсистеме классов, библиотеке или фреймворку.
Проблема
Представьте, что вам нужно заставить ваш код работать с большим количеством объектов, принадлежащих к сложной библиотеке или фреймворку. Обычно вам нужно инициализировать все эти объекты, отслеживать зависимости, выполнять методы в правильном порядке и так далее.
В результате бизнес-логика ваших классов тесно переплетается с деталями реализации сторонних классов, что затрудняет понимание и поддержку кода.
Решение
Паттерн Facade предоставляет простой интерфейс к сложной подсистеме, которая содержит множество классов. Фасад не содержит функциональности подсистемы, он только делегирует вызовы к соответствующим объектам подсистемы.
Структура
Facade
├── subsystem1: Subsystem1
├── subsystem2: Subsystem2
├── subsystem3: Subsystem3
└── operation(): string
Subsystem1
└── operation1(): string
Subsystem2
└── operation2(): string
Subsystem3
└── operation3(): string
Пример реализации
JavaScript
// Подсистемы
class CPU {
freeze() {
console.log('CPU заморожен');
}
jump(position) {
console.log(`CPU переходит к позиции ${position}`);
}
execute() {
console.log('CPU выполняет команды');
}
}
class Memory {
load(position, data) {
console.log(`Загрузка данных в память: позиция ${position}, данные: ${data}`);
}
}
class HardDrive {
read(lba, size) {
console.log(`Чтение с жесткого диска: LBA ${lba}, размер ${size}`);
return 'boot data';
}
}
class ComputerFacade {
constructor() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
start() {
console.log('=== Запуск компьютера ===');
// Последовательность запуска
this.cpu.freeze();
this.memory.load(0, this.hardDrive.read(0, 1024));
this.cpu.jump(0);
this.cpu.execute();
console.log('Компьютер запущен успешно!');
}
shutdown() {
console.log('=== Выключение компьютера ===');
console.log('Сохранение данных...');
console.log('Закрытие программ...');
console.log('Компьютер выключен');
}
}
// Использование
const computer = new ComputerFacade();
// Простой интерфейс для сложной подсистемы
computer.start();
console.log('\nРабота с компьютером...\n');
computer.shutdown();
TypeScript с интерфейсами
// Подсистемы
class OrderService {
createOrder(customerId: number, items: string[]): string {
console.log(`Создание заказа для клиента ${customerId}`);
return `ORDER_${Date.now()}`;
}
}
class PaymentService {
processPayment(orderId: string, amount: number): boolean {
console.log(`Обработка платежа для заказа ${orderId} на сумму ${amount}`);
return true;
}
}
class InventoryService {
checkAvailability(items: string[]): boolean {
console.log(`Проверка наличия товаров: ${items.join(', ')}`);
return true;
}
reserveItems(items: string[]): void {
console.log(`Резервирование товаров: ${items.join(', ')}`);
}
}
class ShippingService {
calculateShipping(address: string): number {
console.log(`Расчет стоимости доставки для адреса: ${address}`);
return 150; // Примерная стоимость доставки
}
shipOrder(orderId: string, address: string): string {
console.log(`Отправка заказа ${orderId} по адресу: ${address}`);
return `TRACKING_${Date.now()}`;
}
}
class NotificationService {
sendConfirmation(customerId: number, orderId: string): void {
console.log(`Отправка подтверждения клиенту ${customerId} для заказа ${orderId}`);
}
sendTrackingInfo(customerId: number, trackingNumber: string): void {
console.log(`Отправка информации об отслеживании клиенту ${customerId}: ${trackingNumber}`);
}
}
// Фасад для электронной коммерции
class ECommerceFacade {
private orderService: OrderService;
private paymentService: PaymentService;
private inventoryService: InventoryService;
private shippingService: ShippingService;
private notificationService: NotificationService;
constructor() {
this.orderService = new OrderService();
this.paymentService = new PaymentService();
this.inventoryService = new InventoryService();
this.shippingService = new ShippingService();
this.notificationService = new NotificationService();
}
placeOrder(customerId: number, items: string[], address: string, amount: number): string {
console.log('=== Оформление заказа ===');
// Проверяем наличие товаров
if (!this.inventoryService.checkAvailability(items)) {
throw new Error('Товары недоступны');
}
// Создаем заказ
const orderId = this.orderService.createOrder(customerId, items);
// Резервируем товары
this.inventoryService.reserveItems(items);
// Обрабатываем платеж
if (!this.paymentService.processPayment(orderId, amount)) {
throw new Error('Ошибка обработки платежа');
}
// Рассчитываем доставку
const shippingCost = this.shippingService.calculateShipping(address);
// Отправляем заказ
const trackingNumber = this.shippingService.shipOrder(orderId, address);
// Уведомляем клиента
this.notificationService.sendConfirmation(customerId, orderId);
this.notificationService.sendTrackingInfo(customerId, trackingNumber);
console.log('Заказ успешно оформлен!');
return orderId;
}
trackOrder(orderId: string): string {
console.log(`Отслеживание заказа ${orderId}`);
return `Статус: В пути, Ожидаемая доставка: завтра`;
}
}
// Использование
const ecommerce = new ECommerceFacade();
try {
const orderId = ecommerce.placeOrder(
12345,
['Ноутбук', 'Мышь', 'Клавиатура'],
'ул. Примерная, 123',
50000
);
console.log(`\nНомер заказа: ${orderId}`);
console.log(ecommerce.trackOrder(orderId));
} catch (error) {
console.error('Ошибка при оформлении заказа:', error.message);
}
Когда использовать
Используйте Facade, когда:
- Нужно предоставить простой интерфейс к сложной подсистеме
- Хотите разделить клиентов и подсистемы на слои
- Нужно скрыть сложность подсистемы от клиентского кода
- Хотите создать точку входа в каждый уровень многоуровневой архитектуры
Преимущества
- Изолирует клиентов от компонентов подсистемы
- Упрощает работу с подсистемой
- Снижает связанность между клиентом и подсистемой
- Предоставляет удобный интерфейс для сложной функциональности
Недостатки
- Может стать "божественным объектом", связанным со всеми классами приложения
- Может скрыть важную функциональность подсистемы
- Может стать слишком сложным, если подсистема очень большая
Отличия от других паттернов
- Adapter — делает несовместимые интерфейсы совместимыми
- Bridge — разделяет абстракцию и реализацию
- Composite — группирует объекты в древовидные структуры
Реальный пример
// Фасад для работы с медиафайлами
class MediaFacade {
constructor() {
this.audioProcessor = new AudioProcessor();
this.videoProcessor = new VideoProcessor();
this.imageProcessor = new ImageProcessor();
this.metadataExtractor = new MetadataExtractor();
}
processMediaFile(filePath, options = {}) {
console.log(`=== Обработка медиафайла: ${filePath} ===`);
// Извлекаем метаданные
const metadata = this.metadataExtractor.extract(filePath);
console.log('Метаданные извлечены');
// Определяем тип файла и обрабатываем соответственно
if (metadata.type === 'audio') {
return this.audioProcessor.process(filePath, options);
} else if (metadata.type === 'video') {
return this.videoProcessor.process(filePath, options);
} else if (metadata.type === 'image') {
return this.imageProcessor.process(filePath, options);
} else {
throw new Error(`Неподдерживаемый тип файла: ${metadata.type}`);
}
}
convertMediaFile(inputPath, outputPath, targetFormat) {
console.log(`=== Конвертация: ${inputPath} -> ${outputPath} ===`);
const metadata = this.metadataExtractor.extract(inputPath);
if (metadata.type === 'audio') {
return this.audioProcessor.convert(inputPath, outputPath, targetFormat);
} else if (metadata.type === 'video') {
return this.videoProcessor.convert(inputPath, outputPath, targetFormat);
} else if (metadata.type === 'image') {
return this.imageProcessor.convert(inputPath, outputPath, targetFormat);
}
}
getMediaInfo(filePath) {
return this.metadataExtractor.extract(filePath);
}
}
// Подсистемы (упрощенные)
class AudioProcessor {
process(filePath, options) {
console.log('Обработка аудиофайла');
return { success: true, duration: '3:45' };
}
convert(inputPath, outputPath, format) {
console.log(`Конвертация аудио в ${format}`);
return { success: true };
}
}
class VideoProcessor {
process(filePath, options) {
console.log('Обработка видеофайла');
return { success: true, duration: '2:30', resolution: '1920x1080' };
}
convert(inputPath, outputPath, format) {
console.log(`Конвертация видео в ${format}`);
return { success: true };
}
}
class ImageProcessor {
process(filePath, options) {
console.log('Обработка изображения');
return { success: true, dimensions: '1920x1080' };
}
convert(inputPath, outputPath, format) {
console.log(`Конвертация изображения в ${format}`);
return { success: true };
}
}
class MetadataExtractor {
extract(filePath) {
const extension = filePath.split('.').pop().toLowerCase();
if (['mp3', 'wav', 'flac'].includes(extension)) {
return { type: 'audio', format: extension, duration: '3:45' };
} else if (['mp4', 'avi', 'mov'].includes(extension)) {
return { type: 'video', format: extension, duration: '2:30' };
} else if (['jpg', 'png', 'gif'].includes(extension)) {
return { type: 'image', format: extension, dimensions: '1920x1080' };
}
return { type: 'unknown', format: extension };
}
}
// Использование
const mediaFacade = new MediaFacade();
// Простая обработка
mediaFacade.processMediaFile('song.mp3');
mediaFacade.processMediaFile('video.mp4');
mediaFacade.processMediaFile('photo.jpg');
// Конвертация
mediaFacade.convertMediaFile('song.mp3', 'song.wav', 'wav');
mediaFacade.convertMediaFile('video.mp4', 'video.avi', 'avi');
// Получение информации
console.log(mediaFacade.getMediaInfo('song.mp3'));
Facade — это простой интерфейс к сложной подсистеме!