Оволодіння шаблонами проектування: Практичні поради для реальних рішень

pic

Шаблони проектування — це перевірені, багаторазові рішення для поширених проблем у програмному проєктуванні. Вони допомагають розробникам створювати масштабовані, зручні в обслуговуванні та ефективні додатки. У цій статті ми розглянемо найпопулярніші шаблони проектування та їх реальні застосування, з прикладами на 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

Leave a Reply

Your email address will not be published. Required fields are marked *