Паттерны проектирования: что такое и зачем
Паттерны проектирования — типовые решения часто встречающихся задач в разработке программного обеспечения: проверенные шаблоны, которые именуют, структурируют и документируют лучшие практики.
Зачем нужно
Паттерны помогают разработчикам общаться на одном языке («Давай используем Singleton» или «Это Observer»), не объясняя каждый раз детали реализации. Они сокращают время на проектирование, уменьшают число ошибок и делают код более предсказуемым для команды.
Где используется
- Архитектура приложения: MVC, MVP, MVVM
- Библиотеки и фреймворки: React (Observer, Strategy), Redux (Command)
- Node.js: Middleware (Chain of Responsibility), EventEmitter (Observer)
- Любой крупный JavaScript-проект
Три категории паттернов (Gang of Four)
1. Порождающие (Creational) — создание объектов
Singleton — один экземпляр на всё приложение:
class Config {
constructor {
if (Config._instance) return Config._instance;
this.settings = {};
Config._instance = this;
}
set(key, value) { this.settings[key] = value; }
get(key) { return this.settings[key]; }
}
const c1 = new Config();
const c2 = new Config();
c1.set('theme', 'dark');
console.log(c2.get('theme')); // 'dark' — тот же объект
console.log(c1 === c2); // true
Factory — создание объектов через фабричный метод:
function createAnimal(type) {
const animals = {
dog: => ({ sound: => 'Гав!' }),
cat: => ({ sound: => 'Мяу!' }),
cow: => ({ sound: => 'Му!' })
};
const factory = animals[type];
if (!factory) throw new Error(`Неизвестный тип: ${type}`);
return factory;
}
const dog = createAnimal('dog');
console.log(dog.sound); // 'Гав!'
2. Структурные (Structural) — организация классов и объектов
Decorator — добавление поведения без изменения класса:
function withLogging(fn) {
return function(...args) {
console.log(`Вызов с аргументами:`, args);
const result = fn.apply(this, args);
console.log(`Результат:`, result);
return result;
};
}
const add = (a, b) => a + b;
const addWithLog = withLogging(add);
addWithLog(2, 3);
// Вызов с аргументами: [2, 3]
// Результат: 5
Facade — упрощённый интерфейс к сложной подсистеме:
class VideoFacade {
constructor {
this.decoder = new VideoDecoder();
this.renderer = new VideoRenderer();
this.audio = new AudioProcessor();
}
play(file) {
const decoded = this.decoder.decode(file);
this.audio.init(decoded.audio);
this.renderer.render(decoded.video);
}
}
const player = new VideoFacade();
player.play('movie.mp4'); // один вызов вместо трёх
3. Поведенческие (Behavioral) — взаимодействие объектов
Observer — подписка на события:
class EventEmitter {
constructor { this._events = {}; }
on(event, listener) {
(this._events[event] = this._events[event] || ).push(listener);
return this;
}
off(event, listener) {
this._events[event] = (this._events[event] || ).filter(l => l !== listener);
}
emit(event, ...args) {
(this._events[event] || ).forEach(l => l(...args));
}
}
const emitter = new EventEmitter();
emitter.on('data', (d) => console.log('Получено:', d));
emitter.emit('data', { id: 1 }); // Получено: { id: 1 }
Strategy — взаимозаменяемые алгоритмы:
const sorters = {
bubble: arr => { /* bubble sort */ return [...arr].sort(); },
quick: arr => [...arr].sort((a, b) => a - b),
merge: arr => [...arr].sort((a, b) => a - b)
};
function sortData(data, strategy = 'quick') {
return sorters[strategy](data);
}
sortData([3, 1, 2], 'quick'); // [1, 2, 3]
sortData([3, 1, 2], 'bubble'); // [1, 2, 3]
Частые ошибки
1. Паттерн ради паттерна (overengineering)
Паттерн решает реальную проблему. Если её нет — добавление паттерна усложняет код без пользы. Singleton, например, часто заменяется простым модулем.
2. Неправильный выбор паттерна
Strategy и State выглядят похоже, но Strategy меняет алгоритм извне, а State меняет поведение на основе внутреннего состояния объекта.