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

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

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

Начать курс

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

Разработка сложных приложений на TypeScript требует не только знания синтаксиса, но и умения строить гибкую, масштабируемую архитектуру. Паттерны проектирования GoF (Gang of Four) остаются золотым стандартом, но на продвинутом уровне их применение становится более тонким. В этой статье мы разберем четыре мощных паттерна — Bridge, Visitor, State и Strategy — с реальными примерами на TypeScript. Вы узнаете, как использовать строгую типизацию, дженерики и интерфейсы для реализации этих паттернов.

1. Bridge: отделяем абстракцию от реализации

Паттерн Bridge (Мост) разделяет абстракцию и её реализацию, позволяя изменять их независимо. Это особенно полезно, когда у вас есть несколько вариаций функциональности, которые могут комбинироваться.

Пример: Система рендеринга фигур в разных графических API (Canvas и SVG).

// Реализация (Implementation)
interface Renderer {
    renderCircle(radius: number): void;
    renderSquare(side: number): void;
}

class CanvasRenderer implements Renderer {
    renderCircle(radius: number): void {
        console.log(`Canvas: рисуем круг радиусом ${radius}`);
    }
    renderSquare(side: number): void {
        console.log(`Canvas: рисуем квадрат со стороной ${side}`);
    }
}

class SVGRenderer implements Renderer {
    renderCircle(radius: number): void {
        console.log(`SVG: <circle r="${radius}" />`);
    }
    renderSquare(side: number): void {
        console.log(`SVG: <rect width="${side}" height="${side}" />`);
    }
}

// Абстракция (Abstraction)
abstract class Shape {
    protected renderer: Renderer;

    constructor(renderer: Renderer) {
        this.renderer = renderer;
    }

    abstract draw(): void;
}

class Circle extends Shape {
    private radius: number;

    constructor(renderer: Renderer, radius: number) {
        super(renderer);
        this.radius = radius;
    }

    draw(): void {
        this.renderer.renderCircle(this.radius);
    }
}

class Square extends Shape {
    private side: number;

    constructor(renderer: Renderer, side: number) {
        super(renderer);
        this.side = side;
    }

    draw(): void {
        this.renderer.renderSquare(this.side);
    }
}

// Использование
const canvasCircle = new Circle(new CanvasRenderer(), 10);
canvasCircle.draw(); // Canvas: рисуем круг радиусом 10

const svgSquare = new Square(new SVGRenderer(), 5);
svgSquare.draw(); // SVG: <rect width="5" height="5" />

Ключевая особенность: TypeScript гарантирует, что все реализации следуют контракту Renderer. Добавление нового типа фигуры или нового рендерера не ломает существующий код.

2. Visitor: обход сложных структур без изменения классов

Паттерн Visitor (Посетитель) позволяет добавлять новые операции к объектам без изменения их классов. Это идеальное решение для работы с деревьями, AST (абстрактными синтаксическими деревьями) или иерархиями компонентов.

Пример: Система экспорта узлов документа в разные форматы (JSON, XML).

// Элементы (Elements)
interface DocumentNode {
    accept(visitor: Visitor): void;
}

class TextNode implements DocumentNode {
    constructor(public content: string) {}

    accept(visitor: Visitor): void {
        visitor.visitTextNode(this);
    }
}

class ImageNode implements DocumentNode {
    constructor(public src: string, public alt: string) {}

    accept(visitor: Visitor): void {
        visitor.visitImageNode(this);
    }
}

// Посетитель (Visitor)
interface Visitor {
    visitTextNode(node: TextNode): void;
    visitImageNode(node: ImageNode): void;
}

class JSONExporter implements Visitor {
    private parts: string[] = [];

    visitTextNode(node: TextNode): void {
        this.parts.push(`{ "type": "text", "content": "${node.content}" }`);
    }

    visitImageNode(node: ImageNode): void {
        this.parts.push(`{ "type": "image", "src": "${node.src}", "alt": "${node.alt}" }`);
    }

    getResult(): string {
        return `[${this.parts.join(', ')}]`;
    }
}

class XMLExporter implements Visitor {
    private parts: string[] = [];

    visitTextNode(node: TextNode): void {
        this.parts.push(`<text>${node.content}</text>`);
    }

    visitImageNode(node: ImageNode): void {
        this.parts.push(`<image src="${node.src}" alt="${node.alt}" />`);
    }

    getResult(): string {
        return `<document>${this.parts.join('')}</document>`;
    }
}

// Использование
const nodes: DocumentNode[] = [
    new TextNode('Привет, мир!'),
    new ImageNode('logo.png', 'Логотип')
];

const jsonExporter = new JSONExporter();
nodes.forEach(node => node.accept(jsonExporter));
console.log(jsonExporter.getResult());
// [{"type": "text", "content": "Привет, мир!"}, {"type": "image", "src": "logo.png", "alt": "Логотип"}]

Важно: TypeScript помогает избежать ошибок — если вы добавите новый тип узла, компилятор заставит реализовать accept и обновить все посетители.

3. State: управление конечными автоматами

Паттерн State (Состояние) позволяет объекту менять своё поведение в зависимости от внутреннего состояния. Это альтернатива громоздким switch/case конструкциям.

Пример: Заказ в интернет-магазине с состояниями: Новый, Оплачен, Отправлен, Доставлен.

interface OrderState {
    pay(order: Order): void;
    ship(order: Order): void;
    deliver(order: Order): void;
    getStatus(): string;
}

class NewState implements OrderState {
    pay(order: Order): void {
        console.log('Заказ оплачен.');
        order.setState(new PaidState());
    }
    ship(order: Order): void {
        console.log('Нельзя отправить неоплаченный заказ.');
    }
    deliver(order: Order): void {
        console.log('Нельзя доставить неоплаченный заказ.');
    }
    getStatus(): string {
        return 'Новый';
    }
}

class PaidState implements OrderState {
    pay(order: Order): void {
        console.log('Заказ уже оплачен.');
    }
    ship(order: Order): void {
        console.log('Заказ отправлен.');
        order.setState(new ShippedState());
    }
    deliver(order: Order): void {
        console.log('Нельзя доставить неотправленный заказ.');
    }
    getStatus(): string {
        return 'Оплачен';
    }
}

class ShippedState implements OrderState {
    pay(order: Order): void {
        console.log('Заказ уже оплачен.');
    }
    ship(order: Order): void {
        console.log('Заказ уже отправлен.');
    }
    deliver(order: Order): void {
        console.log('Заказ до                

Блоги

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