Архитектурные паттерны в TypeScript: от хаоса к чистому коду

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

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

Начать курс

Архитектурные паттерны в TypeScript: как перестать писать "спагетти" и начать жить

Вы когда-нибудь открывали проект на TypeScript, написанный полгода назад, и с ужасом понимали, что не можете разобраться в собственных классах? Или, может быть, вы добавляли новую фичу, а приложение падало в трёх неожиданных местах? Знакомо? Добро пожаловать в клуб разработчиков, которые однажды решили, что "и так сойдёт" — и поплатились за это.

TypeScript — это не просто надстройка над JavaScript с типами. Это мощный инструмент, который позволяет строить сложные, масштабируемые и поддерживаемые системы. Но типы сами по себе не спасут вас от архитектурного хаоса. Нужны архитектурные паттерны — проверенные временем рецепты, которые помогут организовать код так, чтобы он был понятным, тестируемым и гибким.

В этой статье мы разберём пять ключевых архитектурных паттернов TypeScript, которые чаще всего используются в реальных проектах. Вы увидите примеры кода, поймёте, где и зачем применять каждый паттерн, и сможете сразу внедрить их в свою работу. Поехали!

1. MVC (Model-View-Controller) — классика жанра

Начнём с самого известного паттерна. MVC в TypeScript — это разделение приложения на три слоя:

  • Model — данные и бизнес-логика.
  • View — отображение (UI).
  • Controller — связующее звено, обрабатывает ввод пользователя.

В TypeScript приложениях MVC особенно хорош тем, что мы можем строго типизировать все слои, исключая ошибки на этапе компиляции.

Пример: простой список задач (Todo List)

// Model
interface Task {
  id: number;
  title: string;
  completed: boolean;
}

class TaskModel {
  private tasks: Task[] = [];

  addTask(title: string): Task {
    const newTask: Task = {
      id: Date.now(),
      title,
      completed: false,
    };
    this.tasks.push(newTask);
    return newTask;
  }

  getTasks(): Task[] {
    return this.tasks;
  }
}

// View
class TaskView {
  render(tasks: Task[]): void {
    console.clear();
    tasks.forEach(task => {
      console.log(`${task.id}: [${task.completed ? 'x' : ' '}] ${task.title}`);
    });
  }
}

// Controller
class TaskController {
  constructor(
    private model: TaskModel,
    private view: TaskView
  ) {}

  addTask(title: string): void {
    this.model.addTask(title);
    this.view.render(this.model.getTasks());
  }

  showAll(): void {
    this.view.render(this.model.getTasks());
  }
}

// Использование
const model = new TaskModel();
const view = new TaskView();
const controller = new TaskController(model, view);
controller.addTask('Изучить паттерны TypeScript');
controller.addTask('Написать статью');

Почему это круто? Вы можете менять View (например, перейти с консоли на React), не трогая Model. Можете тестировать бизнес-логику отдельно от UI. Архитектура TypeScript приложения становится прозрачной.

2. Observer — король событий

Если ваше приложение живёт за счёт событий (клики, ответы сервера, таймеры), паттерн Observer TypeScript — ваш лучший друг. Он позволяет одним объектам (подписчикам) следить за изменениями другого объекта (издателя).

Пример: система уведомлений

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

// Издатель
class NewsPublisher {
  private observers: Observer[] = [];

  subscribe(observer: Observer): void {
    this.observers.push(observer);
  }

  unsubscribe(observer: Observer): void {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  publishNews(news: string): void {
    this.observers.forEach(obs => obs.update(news));
  }
}

// Конкретный наблюдатель: Email-уведомление
class EmailNotifier implements Observer {
  update(data: string): void {
    console.log(`📧 Email отправлен: ${data}`);
  }
}

// Конкретный наблюдатель: SMS-уведомление
class SMSNotifier implements Observer {
  update(data: string): void {
    console.log(`📱 SMS отправлено: ${data}`);
  }
}

// Использование
const publisher = new NewsPublisher();
const emailNotifier = new EmailNotifier();
const smsNotifier = new SMSNotifier();

publisher.subscribe(emailNotifier);
publisher.subscribe(smsNotifier);

publisher.publishNews('TypeScript 5.0 вышел!');
// 📧 Email отправлен: TypeScript 5.0 вышел!
// 📱 SMS отправлено: TypeScript 5.0 вышел!

Observer паттерн TypeScript идеально ложится на систему типов: мы чётко определяем, какие данные передаются. Это основа реактивного программирования.

3. Factory — когда создание объектов — это сложно

Иногда создание объекта — не просто new ClassName(). Нужно подгрузить конфигурацию, выбрать реализацию, проверить зависимости. Тут на помощь приходит Factory паттерн TypeScript.

Пример: создание подключения к базе данных

// Абстрактный продукт
interface DatabaseConnection {
  connect(): void;
  query(sql: string): any[];
}

// Конкретные продукты
class MySQLConnection implements DatabaseConnection {
  connect(): void { console.log('Подключение к MySQL'); }
  query(sql: string): any[] { return []; }
}

class PostgreSQLConnection implements DatabaseConnection {
  connect(): void { console.log('Подключение к PostgreSQL'); }
  query(sql: string): any[] { return []; }
}

// Фабрика
class DatabaseFactory {
  static createDatabase(type: 'mysql' | 'postgres'): DatabaseConnection {
    switch (type) {
      case 'mysql':
        return new MySQLConnection();
      case 'postgres':
        return new PostgreSQLConnection();
      default:
        throw new Error('Неподдерживаемая БД');
    }
  }
}

// Использование
const db = DatabaseFactory.createDatabase('postgres');
db.connect(); // Подключение к PostgreSQL

Этот архитектурный паттерн TypeScript скрывает сложность создания объектов и делает код гибким. Хотите добавить MongoDB? Просто расширяете фабрику.

4. Repository — прослойка между бизнес-логикой и данными

Repository паттерн TypeScript — это абстракция над хранилищем данных. Вы работаете с коллекциями объектов, не думая, откуда они берутся: из базы, API или кэша.

Пример: работа с пользователями

// Сущность
interface User {
  id: number;
  name: string;
  email: string;
}

// Интерфейс репозитория
interface UserRepository {
  findById(id: number): User | null;
  save(user: User): void;
  delete(id: number): void;
}

// Реализация: работа с массивом в памяти
class InMemoryUserRepository implements UserRepository {
  private users: User[] = [];

  findById(id: number): User | null {
    return this.users.find(u => u.id === id) || null;
  }

  save(user: User): void {
    const index = this.users.findIndex(u => u.id === user.id);
    if (index !== -1) {
      this.users[index] = user;
    } else {                

Блоги

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