Паттерны проектирования в 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