Шаблони проектування — це перевірені, багаторазові рішення для поширених проблем у програмному проєктуванні. Вони допомагають розробникам створювати масштабовані, зручні в обслуговуванні та ефективні додатки. У цій статті ми розглянемо найпопулярніші шаблони проектування та їх реальні застосування, з прикладами на JavaScript та TypeScript.
1. Шаблон "Одиничний екземпляр" (Singleton)
Використання: Шаблон "Одиничний екземпляр" гарантує, що в класі існує лише один екземпляр і надає глобальну точку доступу до нього.
Приклад: Уявіть, що ви створюєте систему журналювання, яка повинна бути доступна в усьому додатку. Ви хочете гарантувати, що існує лише один екземпляр журналу по всьому додатку.
class Logger {
private static instance: Logger;
private constructor() {} // Приватний конструктор, щоб запобігти створенню екземплярів.
public static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
private logMessage(message: string, level: 'info' | 'warning' | 'success' | 'error'): void {
const timestamp = new Date().toISOString();
switch (level) {
case 'info':
console.log(`[INFO] [${timestamp}] ${message}`);
break;
case 'warning':
console.warn(`[WARNING] [${timestamp}] ${message}`);
break;
case 'success':
console.log(`%c[SUCCESS] [${timestamp}] ${message}`, 'color: green; font-weight: bold;');
break;
case 'error':
console.error(`[ERROR] [${timestamp}] ${message}`);
break;
}
}
public info(message: string): void {
this.logMessage(message, 'info');
}
public warn(message: string): void {
this.logMessage(message, 'warning');
}
public success(message: string): void {
this.logMessage(message, 'success');
}
public error(message: string): void {
this.logMessage(message, 'error');
}
}
// Використання
const logger = Logger.getInstance();
logger.info('Це інформаційне повідомлення.');
logger.warn('Це попередження.');
logger.success('Це повідомлення про успіх.');
logger.error('Це повідомлення про помилку.');
Реальне застосування: Системи журналювання, менеджери налаштувань конфігурації, або пули з'єднань до бази даних.
2. Шаблон "Фабричний метод" (Factory Method)
Використання: Шаблон "Фабричний метод" надає інтерфейс для створення об'єктів, але підклас визначає, який клас буде інстанціюватися.
Приклад: Припустимо, ви розробляєте систему для обробки різних типів документів (PDF, Word тощо), і вам потрібно створити фабрику для цих типів документів.
interface Document {
create(): void;
}
class PDFDocument implements Document {
create() {
console.log('Створення PDF документа');
}
}
class WordDocument implements Document {
create() {
console.log('Створення Word документа');
}
}
abstract class DocumentFactory {
abstract createDocument(): Document;
}
class PDFDocumentFactory extends DocumentFactory {
createDocument(): Document {
return new PDFDocument();
}
}
class WordDocumentFactory extends DocumentFactory {
createDocument(): Document {
return new WordDocument();
}
}
// Використання
const pdfFactory = new PDFDocumentFactory();
const pdf = pdfFactory.createDocument();
pdf.create(); // Виведення: Створення PDF документа
Реальне застосування: Системи генерації документів, елементи GUI (кнопки, прапорці тощо), або сервіси сповіщень.
3.
3. Шаблон "Спостерігач" (Observer)
Використання: Шаблон "Спостерігач" визначає залежність між об'єктами так, що коли стан одного об'єкта змінюється, усі його залежні об'єкти отримують сповіщення.
Приклад: Система відстеження цін на акції, де користувачі отримують сповіщення при зміні ціни.
interface Observer {
update(price: number): void;
}
class StockPriceObserver implements Observer {
private name: string;
constructor(name: string) {
this.name = name;
}
update(price: number): void {
console.log(`${this.name} повідомлений про зміну ціни акції: $${price}`);
}
}
class StockPriceSubject {
private observers: Observer[] = [];
addObserver(observer: Observer): void {
this.observers.push(observer);
}
removeObserver(observer: Observer): void {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(price: number): void {
this.observers.forEach(observer => observer.update(price));
}
}
// Використання
const stockPriceSubject = new StockPriceSubject();
const observer1 = new StockPriceObserver('Спостерігач 1');
const observer2 = new StockPriceObserver('Спостерігач 2');
stockPriceSubject.addObserver(observer1);
stockPriceSubject.addObserver(observer2);
stockPriceSubject.notify(100);
// Виведення: Спостерігач 1 повідомлений про зміну ціни акції: $100
// Виведення: Спостерігач 2 повідомлений про зміну ціни акції: $100
Реальне застосування: Системи обробки подій, фреймворки для інтерфейсів користувача (як React), підписки на новини/блоги.
4. Шаблон "Стратегії" (Strategy)
Використання: Шаблон "Стратегія" визначає сімейство алгоритмів і дозволяє їх змінювати в межах заданого контексту.
Приклад: Уявіть онлайн-систему для оплат, яка підтримує різні методи оплати (кредитна картка, PayPal тощо).
interface PaymentStrategy {
pay(amount: number): void;
}
class CreditCardPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`Оплата ${amount} за допомогою кредитної картки`);
}
}
class PayPalPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`Оплата ${amount} за допомогою PayPal`);
}
}
class PaymentContext {
private strategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: PaymentStrategy) {
this.strategy = strategy;
}
executePayment(amount: number): void {
this.strategy.pay(amount);
}
}
// Використання
const payment = new PaymentContext(new CreditCardPayment());
payment.executePayment(50); // Виведення: Оплата 50 за допомогою кредитної картки
payment.setStrategy(new PayPalPayment());
payment.executePayment(100); // Виведення: Оплата 100 за допомогою PayPal
Реальне застосування: Платіжні системи, алгоритми маршрутизації, або стратегії сортування.
5.
5. Шаблон "Декоратор" (Decorator)
Використання: Шаблон "Декоратор" дозволяє динамічно додавати нову функціональність об'єкту без зміни його структури.
Приклад: Уявімо, що ви створюєте систему форматування тексту, де хочете додавати нові поведінки форматування динамічно.
interface Text {
getContent(): string;
}
class PlainText implements Text {
getContent(): string {
return 'Привіт, Світ!';
}
}
class TextDecorator implements Text {
constructor(private text: Text) {}
getContent(): string {
return this.text.getContent();
}
}
class BoldDecorator extends TextDecorator {
getContent(): string {
return `${this.text.getContent()}`;
}
}
class ItalicDecorator extends TextDecorator {
getContent(): string {
return `${this.text.getContent()}`;
}
}
// Використання
const plainText = new PlainText();
console.log(plainText.getContent()); // Виведення: Привіт, Світ!
const boldText = new BoldDecorator(plainText);
console.log(boldText.getContent()); // Виведення: Привіт, Світ!
const italicBoldText = new ItalicDecorator(boldText);
console.log(italicBoldText.getContent()); // Виведення: Привіт, Світ!
Реальне застосування: Налаштування компонентів UI (кнопок, тексту тощо), логування з різними рівнями (помилка, інформація, відлагодження).
6. Шаблон "Команда" (Command)
Використання: Шаблон "Команда" інкапсулює запит як об'єкт, дозволяючи параметризувати клієнтів різними запитами.
Приклад: Уявімо просту систему дистанційного керування, де кожна кнопка на пульті відповідає певній дії (увімкнення/вимкнення світла).
interface Command {
execute(): void;
}
class LightOnCommand implements Command {
private light: Light;
constructor(light: Light) {
this.light = light;
}
execute(): void {
this.light.turnOn();
}
}
class LightOffCommand implements Command {
private light: Light;
constructor(light: Light) {
this.light = light;
}
execute(): void {
this.light.turnOff();
}
}
class Light {
turnOn() {
console.log('Світло вмикнене');
}
turnOff() {
console.log('Світло вимкнене');
}
}
class RemoteControl {
private command: Command;
setCommand(command: Command) {
this.command = command;
}
pressButton() {
this.command.execute();
}
}
// Використання
const light = new Light();
const lightOn = new LightOnCommand(light);
const lightOff = new LightOffCommand(light);
const remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton(); // Виведення: Світло вмикнене
remote.setCommand(lightOff);
remote.pressButton(); // Виведення: Світло вимкнене
Реальне застосування: Дії в GUI, функціональність скасування/повторення, або планування завдань.
Висновок
Шаблони проектування є важливими інструментами для покращення підтримуваності та гнучкості вашого коду. Застосовуючи ці шаблони до реальних додатків, ви можете ефективно вирішувати поширені проблеми проектування програмного забезпечення. Почніть інтегрувати ці шаблони у вашу щоденну роботу з розробки, і з часом ви побачите покращення якості коду та продуктивності розробників.
Перекладено з: Mastering Design Patterns: Practical Insights for Real-World Solutions