Паттерны проектирования в TypeScript: продвинутый уровень с примерами

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

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

Начать курс

Паттерны проектирования в TypeScript: продвинутый уровень с примерами

Когда базовые паттерны (Одиночка, Фабричный метод, Наблюдатель) освоены, наступает время для более сложных архитектурных решений. TypeScript с его мощной системой типов, дженериками и декораторами открывает новые возможности для реализации продвинутых паттернов GoF. В этой статье мы разберём четыре ключевых паттерна: Абстрактную фабрику, Строителя, Цепочку обязанностей и Стратегию — с акцентом на TypeScript-специфичные фишки.

1. Абстрактная фабрика (Abstract Factory) с типизированными семействами

Паттерн «Абстрактная фабрика» позволяет создавать семейства взаимосвязанных объектов без привязки к конкретным классам. В TypeScript мы можем использовать интерфейсы и дженерики для строгой типизации продуктов.

Пример: Создание UI-компонентов для разных операционных систем.

// Абстрактные продукты
interface Button {
  render(): void;
  click(): void;
}

interface Checkbox {
  render(): void;
  toggle(): void;
}

// Конкретные продукты для Windows
class WinButton implements Button {
  render() { console.log('Отрисовка кнопки Windows'); }
  click() { console.log('Клик по кнопке Windows'); }
}

class WinCheckbox implements Checkbox {
  render() { console.log('Отрисовка чекбокса Windows'); }
  toggle() { console.log('Переключение чекбокса Windows'); }
}

// Конкретные продукты для macOS
class MacButton implements Button {
  render() { console.log('Отрисовка кнопки macOS'); }
  click() { console.log('Клик по кнопке macOS'); }
}

class MacCheckbox implements Checkbox {
  render() { console.log('Отрисовка чекбокса macOS'); }
  toggle() { console.log('Переключение чекбокса macOS'); }
}

// Абстрактная фабрика
interface GUIFactory {
  createButton(): Button;
  createCheckbox(): Checkbox;
}

// Конкретные фабрики
class WinFactory implements GUIFactory {
  createButton(): Button { return new WinButton(); }
  createCheckbox(): Checkbox { return new WinCheckbox(); }
}

class MacFactory implements GUIFactory {
  createButton(): Button { return new MacButton(); }
  createCheckbox(): Checkbox { return new MacCheckbox(); }
}

// Использование
function createUI(factory: GUIFactory) {
  const button = factory.createButton();
  const checkbox = factory.createCheckbox();
  button.render();
  checkbox.render();
}

createUI(new WinFactory());
// Отрисовка кнопки Windows
// Отрисовка чекбокса Windows

Продвинутый приём: Используйте дженерики, если фабрика должна работать с разными типами продуктов:

abstract class AbstractFactory<TProduct> {
  abstract createProduct(): TProduct;
}

class ConcreteFactory extends AbstractFactory<Button> {
  createProduct(): Button {
    return new WinButton();
  }
}

2. Строитель (Builder) с fluent-интерфейсом и иммутабельностью

Паттерн «Строитель» полезен, когда объект имеет множество опциональных параметров. TypeScript позволяет создать типобезопасный builder с цепочкой методов.

Пример: Конфигурация HTTP-запроса.

class HttpRequest {
  private constructor(
    public readonly url: string,
    public readonly method: 'GET' | 'POST' | 'PUT',
    public readonly headers: Record<string, string>,
    public readonly body?: string
  ) {}

  static builder(): HttpRequestBuilder {
    return new HttpRequestBuilder();
  }
}

class HttpRequestBuilder {
  private url: string = '';
  private method: 'GET' | 'POST' | 'PUT' = 'GET';
  private headers: Record<string, string> = {};
  private body?: string;

  setUrl(url: string): this {
    this.url = url;
    return this;
  }

  setMethod(method: 'GET' | 'POST' | 'PUT'): this {
    this.method = method;
    return this;
  }

  addHeader(key: string, value: string): this {
    this.headers[key] = value;
    return this;
  }

  setBody(body: string): this {
    this.body = body;
    return this;
  }

  build(): HttpRequest {
    if (!this.url) {
      throw new Error('URL обязателен');
    }
    return new HttpRequest(this.url, this.method, this.headers, this.body);
  }
}

// Использование
const request = HttpRequest.builder()
  .setUrl('https://api.example.com/users')
  .setMethod('POST')
  .addHeader('Content-Type', 'application/json')
  .setBody(JSON.stringify({ name: 'Alice' }))
  .build();

console.log(request);
// HttpRequest { url: 'https://api.example.com/users', method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{"name":"Alice"}' }

Продвинутый приём: Используйте Partial<T> и Pick<T, K> для создания частичных builders, если нужно строго контролировать порядок установки свойств.

3. Цепочка обязанностей (Chain of Responsibility) с дженериками

Паттерн «Цепочка обязанностей» позволяет передавать запрос по цепочке обработчиков. TypeScript даёт возможность сделать цепочку типобезопасной с помощью дженериков.

Пример: Валидация данных формы.

// Базовый интерфейс обработчика
interface Handler<TInput, TOutput> {
  setNext(handler: Handler<TInput, TOutput>): Handler<TInput, TOutput>;
  handle(input: TInput): TOutput | null;
}

// Абстрактный класс для удобства
abstract class AbstractHandler<TInput, TOutput> implements Handler<TInput, TOutput> {
  private nextHandler: Handler<TInput, TOutput> | null = null;

  setNext(handler: Handler<TInput, TOutput>): Handler<TInput, TOutput> {
    this.nextHandler = handler;
    return handler;
  }

  handle(input: TInput): TOutput | null {
    if (this.nextHandler) {
      return this.nextHandler.handle(input);
    }
    return null;
  }
}

// Конкретные обработчики
class EmailValidator extends AbstractHandler<string, string> {
  handle(email: string): string | null {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
      console.log('Email невалиден');
      return null;
    }
    console.log('Email валиден');
    return super.handle(email);
  }
}

class PasswordValidator extends AbstractHandler<string, string> {
  handle(password: string): string | null {
    if (password.length < 8) {
      console                

Блоги

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