Паттерны проектирования в TypeScript: примеры и реализация

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Паттерны проектирования в TypeScript: от теории к практике

Паттерны проектирования (Design Patterns) — это проверенные решения типовых задач в разработке программного обеспечения. В контексте TypeScript, с его мощной системой типов и поддержкой ООП, многие классические паттерны из книги "Банды четырёх" (GoF) получают новое, более элегантное воплощение. В этой статье мы разберём три ключевых паттерна с реальными примерами на TypeScript, которые помогут вам писать более чистый, масштабируемый и поддерживаемый код.

1. Порождающий паттерн: Singleton (Одиночка)

Назначение: Гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему.

В TypeScript реализация Singleton особенно удобна благодаря статическим свойствам и приватным конструкторам. Этот паттерн идеален для управления подключениями к базе данных, логгерами или кэшами.

class DatabaseConnection {
    private static instance: DatabaseConnection;
    private connectionString: string;

    private constructor(connectionString: string) {
        this.connectionString = connectionString;
        console.log(`Создано подключение к: ${connectionString}`);
    }

    public static getInstance(connectionString?: string): DatabaseConnection {
        if (!DatabaseConnection.instance) {
            if (!connectionString) {
                throw new Error("Connection string required for first initialization");
            }
            DatabaseConnection.instance = new DatabaseConnection(connectionString);
        }
        return DatabaseConnection.instance;
    }

    public query(sql: string): void {
        console.log(`Выполнение запроса: ${sql} на ${this.connectionString}`);
    }
}

// Использование
const db1 = DatabaseConnection.getInstance("postgres://localhost:5432/mydb");
const db2 = DatabaseConnection.getInstance();

console.log(db1 === db2); // true — один и тот же объект
db1.query("SELECT * FROM users");

Важно: В TypeScript можно использовать readonly и дженерики для более гибких реализаций. Однако помните, что Singleton может усложнить тестирование — рассмотрите внедрение зависимостей (DI) как альтернативу.

2. Структурный паттерн: Decorator (Декоратор)

Назначение: Динамически добавляет объекту новые обязанности, не изменяя его структуру. В TypeScript декораторы — это первоклассная возможность языка (экспериментальная в ES5, но активно используется в Angular и NestJS).

Рассмотрим пример с декоратором для логирования вызовов методов:

// Целевой интерфейс
interface ILogger {
    log(message: string): void;
}

// Базовый класс
class SimpleLogger implements ILogger {
    public log(message: string): void {
        console.log(`[LOG]: ${message}`);
    }
}

// Декоратор (класс-обёртка)
class TimestampDecorator implements ILogger {
    private logger: ILogger;

    constructor(logger: ILogger) {
        this.logger = logger;
    }

    public log(message: string): void {
        const timestamp = new Date().toISOString();
        this.logger.log(`[${timestamp}] ${message}`);
    }
}

// Декоратор для форматирования
class UpperCaseDecorator implements ILogger {
    private logger: ILogger;

    constructor(logger: ILogger) {
        this.logger = logger;
    }

    public log(message: string): void {
        this.logger.log(message.toUpperCase());
    }
}

// Использование
let logger: ILogger = new SimpleLogger();
logger.log("Привет мир"); // [LOG]: Привет мир

logger = new TimestampDecorator(logger);
logger = new UpperCaseDecorator(logger);
logger.log("декораторы работают"); 
// [LOG]: [2025-01-15T10:30:00.000Z] ДЕКОРАТОРЫ РАБОТАЮТ

Совет: В TypeScript также доступны декораторы классов и свойств (синтаксис @decorator). Они активно используются в Angular для внедрения зависимостей и метаданных, но требуют включения experimentalDecorators в tsconfig.json.

3. Поведенческий паттерн: Observer (Наблюдатель)

Назначение: Определяет зависимость "один ко многим" между объектами, так что при изменении состояния одного объекта все зависящие от него уведомляются и обновляются автоматически.

В TypeScript этот паттерн отлично ложится на систему событий (EventEmitter) и RxJS. Рассмотрим простую реализацию:

// Интерфейс наблюдателя
interface Observer<T> {
    update(data: T): void;
}

// Субъект (издатель)
class Subject<T> {
    private observers: Observer<T>[] = [];
    private state: T;

    public attach(observer: Observer<T>): void {
        const isExist = this.observers.includes(observer);
        if (!isExist) {
            this.observers.push(observer);
        }
    }

    public detach(observer: Observer<T>): void {
        this.observers = this.observers.filter(obs => obs !== observer);
    }

    public notify(): void {
        for (const observer of this.observers) {
            observer.update(this.state);
        }
    }

    public setState(newState: T): void {
        this.state = newState;
        console.log(`Состояние изменилось на: ${newState}`);
        this.notify();
    }
}

// Конкретный наблюдатель
class UserNotifier implements Observer<string> {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    public update(data: string): void {
        console.log(`[${this.name}] Получено обновление: ${data}`);
    }
}

// Использование
const newsFeed = new Subject<string>();

const user1 = new UserNotifier("Алиса");
const user2 = new UserNotifier("Боб");

newsFeed.attach(user1);
newsFeed.attach(user2);

newsFeed.setState("Новая статья: Паттерны в TypeScript");
// Вывод:
// Состояние изменилось на: Новая статья: Паттерны в TypeScript
// [Алиса] Получено обновление: Новая статья: Паттерны в TypeScript
// [Боб] Получено обновление: Новая статья: Паттерны в TypeScript

newsFeed.detach(user1);
newsFeed.setState("Обновление цен на курсы");
// Боб получит уведомление, Алиса — нет

Практика: В реальных проектах на TypeScript (особенно в Angular) Observer паттерн реализуется через Subject из RxJS. Это даёт мощные возможности для работы с асинхронными потоками данных.

4. Паттерн Strategy (Стратегия) — гибкая замена switch-case

Назначение: Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Паттерн позволяет изменять поведение объекта во время выполнения.

// Интерфейс стратегии
interface PaymentStrategy {
    pay(amount: number): void;
}

// Конкретные стратегии
class CreditCardPayment                

Блоги

Рекомендаций по книгам