Lapa Knowledge Base
Структурные паттерны

Facade

Паттерн 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 — это простой интерфейс к сложной подсистеме!

Copyright © 2026