Facade Pattern — Фасад
Простой интерфейс к сложной подсистеме. Главный паттерн архитектурных границ по автору — «полюбите его больше остальных».
Проблема
Подсистема состоит из десятков/сотен классов. Клиент не должен знать про каждый. Нужно:
- упростить API
- защитить внутренности от прямого доступа
- сделать архитектурные границы между слоями
Сложные подсистемы (IndexedDB, WebAudio API, сетевой слой с retry/кэшированием/авторизацией) требуют десятков вызовов для одной логической операции. Facade превращает это в db.getUser(id).
Где используется
- HTTP-клиент: Facade над
fetchс авторизацией, retry, error handling - IndexedDB: упрощение сложного асинхронного API
- Аудио/видео: Facade над Web Audio API или MediaStream API
- Уведомления: единый API для Web Push, email, SMS
- Библиотеки и SDK:
axios,firebase/app— готовые Facade
Решение
Один класс/функция/модуль с простым публичным API. Внутри:
- собирает много классов в логически связанную подсистему
- делегирует операции внутрь
- скрывает сложность
Реализации
TimeoutCollection (короткий пример)
class Collection { /* ... */ }
class TimerManager { /* ... */ }
class ExpirationPolicy { /* ... */ }
class TimeoutCollection {
#collection = new Collection();
#timers = new TimerManager();
#policy;
constructor(timeout) { this.#policy = new ExpirationPolicy(timeout); }
set(key, value) {
this.#collection.set(key, value);
this.#timers.schedule(key, () => this.#collection.delete(key), this.#policy.timeout);
}
get(key) { return this.#collection.get(key); }
delete(key) {
this.#timers.cancel(key);
this.#collection.delete(key);
}
toArray { return this.#collection.toArray; }
}
const cache = new TimeoutCollection(1000);
cache.set('key', 'value');
HTTP-клиент
class HttpFacade {
constructor(baseUrl, options = {}) {
this.baseUrl = baseUrl;
this.retries = options.retries ?? 3;
this.timeout = options.timeout ?? 10000;
}
async #fetchWithRetry(url, options, retries) {
const controller = new AbortController();
const timeoutId = setTimeout( => controller.abort(), this.timeout);
try {
const res = await fetch(url, { ...options, signal: controller.signal });
clearTimeout(timeoutId);
if (!res.ok) {
if (res.status >= 500 && retries > 0) {
await new Promise(r => setTimeout(r, 1000));
return this.#fetchWithRetry(url, options, retries - 1);
}
throw new Error(`HTTP ${res.status}`);
}
return res.json();
} catch (err) {
clearTimeout(timeoutId);
throw err;
}
}
#getHeaders {
const token = localStorage.getItem('token');
return {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {})
};
}
get(path) {
return this.#fetchWithRetry(`${this.baseUrl}${path}`, { method: 'GET', headers: this.#getHeaders }, this.retries);
}
post(path, body) {
return this.#fetchWithRetry(`${this.baseUrl}${path}`, { method: 'POST', headers: this.#getHeaders, body: JSON.stringify(body) }, this.retries);
}
}
const api = new HttpFacade('https://api.example.com');
const user = await api.get('/users/1');
Facade для IndexedDB
class IndexedDBFacade {
constructor(dbName, version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
async open(stores = ) {
return new Promise((resolve, reject) => {
const req = indexedDB.open(this.dbName, this.version);
req.onupgradeneeded = (e) => {
const db = e.target.result;
stores.forEach(s => { if (!db.objectStoreNames.contains(s)) db.createObjectStore(s, { keyPath: 'id' }); });
};
req.onsuccess = (e) => { this.db = e.target.result; resolve(this); };
req.onerror = (e) => reject(e.target.error);
});
}
async get(store, id) {
return new Promise((resolve, reject) => {
const tx = this.db.transaction(store, 'readonly');
const req = tx.objectStore(store).get(id);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
async put(store, item) {
return new Promise((resolve, reject) => {
const tx = this.db.transaction(store, 'readwrite');
const req = tx.objectStore(store).put(item);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
}
Где используется в JS-экосистеме
- jQuery — фасад над DOM, AJAX, animations
- Lodash/Underscore — фасад над работой с коллекциями
- Axios — фасад над XMLHttpRequest/fetch
- Express App — фасад над HTTP-server
- API Gateway — фасад на уровне микросервисов
Подводные камни
- Facade vs Adapter: Adapter — одна абстракция за другой; Facade — много за одной.
- Facade vs Composite: Composite — дерево однотипных узлов; Facade — несвязанные классы за одним API.
- God object antipattern: если фасад делает всё подряд — это не фасад, а помойка.
- Фасад должен скрывать логически связанную подсистему — не группировать просто по факту существования.
- Facade скрывает полезный API: если клиентам нужен доступ к деталям, предоставьте через свойство.
Главные тезисы автора
- «Этот паттерн вы все должны полюбить больше остальных».
- «Декомпозиция кода на модули и отделение их друг от друга контрактами».
- Архитектурные границы: «все слои друг от друга должны быть отделены фасадами».
- «Нужен банан — получаешь обезьяну с пальмой и тропический лес» — фасад отдаёт только нужное.
- API Gateway = Facade на уровне микросервисов — тот же принцип, другой уровень.
- Фасад в JS может быть: функцией, фабрикой, классом, прототипом, модулем — что угодно.
- «Самое главное не в том, что взяли пачку классов и скрыли. Они должны быть связаны логически».
🎓 Источники
- 🎓 Фасад — паттерн для скрытия сложности · 2019-03-12
- «Полюбите этот паттерн больше остальных»
- TimeoutCollection как пример
- Реализация на классах, прототипах, фабрике-функции
- 🎓 GoF Patterns Обзор всех паттернов · 2025-04-29
- Facade в чистой архитектуре
- Метафора банана и обезьяны
- Facade vs API Gateway
- 🎓 Адаптер, Декоратор, Прокси, Фасад — В чём разница · 2026-02-09
- refactoring.guru — Facade