Вы когда-нибудь ловили себя на мысли, что пишете один и тот же код снова и снова? Или что ваш проект превращается в запутанный клубок, в котором страшно что-то менять? Паттерны проектирования — это проверенные рецепты, которые помогают избежать таких проблем. В этой статье мы разберём основные паттерны на примерах из JavaScript, чтобы вы могли применять их в реальных проектах.
Что такое паттерны проектирования и зачем они нужны
Паттерн проектирования — это типовое решение часто возникающей проблемы при проектировании программного обеспечения. Это не готовый код, а шаблон, который можно адаптировать под свою задачу. Паттерны помогают разработчикам общаться на одном языке: когда вы говорите «здесь нужен Singleton», коллега сразу понимает, о чём речь.
Зачем их использовать? Во-первых, они проверены временем и сообществом. Во-вторых, они делают код более предсказуемым и поддерживаемым. В-третьих, они упрощают коммуникацию в команде. Но не стоит пихать паттерны везде — это может усложнить код. Используйте их там, где они действительно решают проблему.
Порождающие паттерны: Singleton и Factory
Порождающие паттерны отвечают за создание объектов. Они помогают сделать систему гибкой и независимой от конкретных классов.
Singleton (Одиночка)
Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В JavaScript это можно реализовать с помощью замыкания или статического свойства.
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
this.data = [];
}
add(item) {
this.data.push(item);
}
getData() {
return this.data;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
Singleton полезен, когда нужен общий доступ к какому-то ресурсу, например, к соединению с базой данных или к конфигурации приложения. Но будьте осторожны: он вводит глобальное состояние, что может усложнить тестирование.
Factory (Фабрика)
Фабрика предоставляет интерфейс для создания объектов, но позволяет подклассам изменять тип создаваемых объектов. В JavaScript фабрику часто реализуют как функцию, возвращающую новый объект.
function createUser(type) {
if (type === 'admin') {
return { role: 'admin', permissions: ['read', 'write', 'delete'] };
} else if (type === 'user') {
return { role: 'user', permissions: ['read'] };
} else {
throw new Error('Unknown user type');
}
}
const admin = createUser('admin');
const user = createUser('user');
Фабрика удобна, когда создание объекта требует сложной логики или когда нужно скрыть детали реализации.
Структурные паттерны: Adapter и Decorator
Структурные паттерны описывают, как классы и объекты объединяются в более крупные структуры.
Adapter (Адаптер)
Адаптер позволяет объектам с несовместимыми интерфейсами работать вместе. Это как переходник для розетки: вы вставляете вилку от европейского прибора в советскую розетку через адаптер.
// Старый интерфейс
class OldPrinter {
printOld(text) {
console.log('Old printer: ' + text);
}
}
// Новый интерфейс
class NewPrinter {
print(text) {
console.log('New printer: ' + text);
}
}
// Адаптер
class PrinterAdapter {
constructor(oldPrinter) {
this.oldPrinter = oldPrinter;
}
print(text) {
this.oldPrinter.printOld(text);
}
}
const oldPrinter = new OldPrinter();
const adapter = new PrinterAdapter(oldPrinter);
adapter.print('Hello'); // Old printer: Hello
Адаптер часто используется при интеграции сторонних библиотек или при работе с устаревшим кодом.
Decorator (Декоратор)
Декоратор добавляет объекту новые обязанности динамически, не изменяя его класс. В JavaScript декораторы можно реализовать с помощью функций высшего порядка или специального синтаксиса (если используется TypeScript или Babel).
function readonly(target, key, descriptor) {
descriptor.writable = false;
return descriptor;
}
class User {
constructor(name) {
this.name = name;
}
@readonly
getName() {
return this.name;
}
}
Декораторы полезны для добавления логирования, кэширования, проверки прав доступа и т.д.
Поведенческие паттерны: Observer и Strategy
Поведенческие паттерны определяют взаимодействие между объектами и распределение обязанностей.
Observer (Наблюдатель)
Наблюдатель создаёт механизм подписки, позволяющий одним объектам следить за изменениями других. Это основа событийно-ориентированного программирования.
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
update(data) {
console.log('Observer received:', data);
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Hello'); // Observer received: Hello (дважды)
Observer часто используется в UI-фреймворках, в системах обработки событий, для реактивного программирования.
Strategy (Стратегия)
Стратегия позволяет выбирать алгоритм поведения во время выполнения. Вы определяете семейство алгоритмов, инкапсулируете каждый и делаете их взаимозаменяемыми.
const strategies = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b
};
function calculate(strategy, a, b) {
return strategies[strategy](a, b);
}
console.log(calculate('add', 5, 3)); // 8
console.log(calculate('multiply', 5, 3)); // 15
Стратегия полезна, когда у вас есть несколько вариантов одного и того же действия, и вы хотите избежать множественных условий if-else.
Сравнение паттернов: когда какой использовать
Чтобы легче было ориентироваться, вот таблица с краткими характеристиками рассмотренных паттернов.
| Паттерн | Тип | Когда использовать |
|---|---|---|
| Singleton | Порождающий | Нужен единственный экземпляр класса (конфигурация, пул соединений) |
| Factory | Порождающий | Создание объектов со сложной логикой или скрытие конкретных классов |
| Adapter | Структурный | Интеграция несовместимых интерфейсов |
| Decorator | Структурный | Динамическое добавление обязанностей без изменения класса |
| Observer | Поведенческий | Организация подписки на события |
| Strategy | Поведенческий | Выбор алгоритма во время выполнения |
Заключение
Паттерны проектирования — это мощный инструмент в руках разработчика. Они помогают писать чистый, понятный и расширяемый код. Но помните: паттерны — это не серебряная пуля. Используйте их осознанно, только когда они действительно упрощают решение задачи. Начните с малого: попробуйте применить Singleton для конфигурации или Observer для обработки событий. Со временем вы научитесь интуитивно выбирать подходящий паттерн.
Удачи в кодинге!



