Досить легко опинитися в лабіринті складних взаємозв’язків у проекті, особливо якщо це проект в IT і якщо ви тільки новачок. Щоб не заплутатися в сотнях рядків коду, варто знайомитися з патернами проектування, які стануть вам в нагоді навіть коли ви підніметеся до рівня senior. Вони показують перевірені підходи до організації класів, об’єктів і взаємодії між ними.
Ось сім базових патернів проектування, знання яких полегшить розвиток і підтримку вашого коду:
- Singleton
- Factory Method
- Observer
- Strategy
- Decorator
- Adapter
- MVC
Розберемо їх детальніше та наведемо приклади на JS.
Singleton
Singleton — простий спосіб зробити так, що клас матиме лише один екземпляр, до якого можна дістатися з будь-якої частини програми. Він часто використовується для керування конфігурацією або підключенням до бази даних.
На JavaScript це виглядає так:
// Модуль для єдиного екземпляра
const SingletonModule = (function() {
let instanceReference = null;
function createInstance() {
// Генеруємо випадкове число
return { generated: Math.random() };
}
return {
getInstance() {
if (instanceReference === null) {
instanceReference = createInstance();
}
return instanceReference;
}
};
})();
// Перевіряємо, що повертається один і той же об'єкт
const first = SingletonModule.getInstance();
const second = SingletonModule.getInstance();
console.log(first === second); // true
console.log(first.generated, second.generated); // однкові значення
Singleton корисний, коли потрібен єдиний глобальний об’єкт, але зловживати ним не варто — надмірне використання ускладнює тестування.
Factory Method
Патерн Factory Method делегує створення об’єктів підкласам, тим самим він забезпечує інкапсуляцію процесу інстанціації. Замість прямого виклику конструктора клієнт звертається до фабричного методу, що повертає потрібний тип.
Наприклад, у месенджері можна мати фабрику повідомлень:
class TextMessage {
send() { console.log('Відправлено повідомлення'); }
}
class ImageMessage {
send() { console.log('Відправлено зображення'); }
}
class MessageFactory {
static create(type) {
switch (type) {
case 'text': return new TextMessage();
case 'image': return new ImageMessage();
default: throw new Error('Невідомий тип повідомлення');
}
}
}
// Використання
const msg1 = MessageFactory.create('text');
msg1.send(); // Відправлено текстове повідомлення
const msg2 = MessageFactory.create('image');
msg2.send(); // Відправлено зображення
Завдяки Factory Method додати новий тип повідомлення можна без зміни коду клієнта.
Observer
Observer дозволяє об’єктам підписуватися на зміни в іншому об’єкті й отримувати повідомлення автоматично. Наприклад, UI-компоненти реагують на зміну даних у моделі.
У JavaScript це виглядає так:
class Subject {
constructor() { this.observers = []; }
subscribe(o) { this.observers.push(o); }
notify(data) { this.observers.forEach(o => o.update(data)); }
}
Strategy
Завдяки Strategy можна вибрати алгоритм під час виконання програми, бо він кожен інкапсулює у власний клас. Завдяки цьому код більш гнучкий.
Приклад на JS:
// Різні стратегії
const fastStrategy = {
compress(data) { return `Fast compress: ${data}`; }
};
const slowStrategy = {
compress(data) { return `Slow compress: ${data}`; }
};
class Compressor {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
compress(data) {
return this.strategy.compress(data);
}
}
// Використання
const compressor = new Compressor(fastStrategy);
console.log(compressor.compress('file.txt')); // Fast compress: file.txt
compressor.setStrategy(slowStrategy);
console.log(compressor.compress('file.txt')); // Slow compress: file.txt
Decorator
Патерн Decorator допомагає динамічно додавати об’єктам нову поведінку без зміни їхнього коду. Кожен декоратор огортає базовий об’єкт і доповнює його. На JS це виглядає так:
class Notifier {
send(message) { console.log(`Email: ${message}`); }
}
function withSMS(notifier) {
return {
send(message) {
notifier.send(message);
console.log(`SMS: ${message}`);
}
};
}
// Приклад
const emailNotifier = new Notifier();
const fullNotifier = withSMS(emailNotifier);
fullNotifier.send('Привіт!');
// Email: Привіт!
// SMS: Привіт!
Decorator практично ідеально підходить для поступового розширення функціональності.
Adapter
Adapter «перекладає» інтерфейс одного класу в інтерфейс, який очікує клієнт. Це зручно при інтеграції сторонніх бібліотек або при зміні API.
// Старий інтерфейс
class OldLogger {
logInfo(msg) { console.log(`INFO: ${msg}`); }
}
// Новий сервіс з іншим інтерфейсом
class NewLoggerService {
write(level, msg) { console.log(`${level.toUpperCase()}: ${msg}`); }
}
// Адаптер
class LoggerAdapter {
constructor(service) {
this.service = service;
}
logInfo(msg) {
this.service.write('info', msg);
}
}
// Використання
const newService = new NewLoggerService();
const logger = new LoggerAdapter(newService);
logger.logInfo('Системний запуск');
// INFO: Системний запуск
Завдяки Adapter ви зберігаєте старий код без змін та підключаєте нові модулі.
MVC
Model-View-Controller (MVC) — це архітектурний патерн, що розділяє дані (Model), відображення (View) і логіку керування (Controller). Це ставить чіткі межі між шарами в веб- і десктопних додатках. MVC спрощує тестування і командну розробку завдяки чіткому розподілу обов’язків.
Ці сім патернів — ваш надійний інструмент для побудови чистого та гнучкого коду. Почніть застосовувати їх на практиці вже сьогодні, і ваші проєкти стануть більш структурованими.